mirror of
https://github.com/mathuo/dockview
synced 2025-02-10 10:25:46 +00:00
Merge branch 'master' of https://github.com/mathuo/dockview into 263-left-header-actions
This commit is contained in:
commit
359b0e81d0
@ -13,6 +13,7 @@
|
||||
"/packages/docs/sandboxes/externaldnd-dockview",
|
||||
"/packages/docs/sandboxes/fullwidthtab-dockview",
|
||||
"/packages/docs/sandboxes/groupcontol-dockview",
|
||||
"/packages/docs/sandboxes/iframe-dockview",
|
||||
"/packages/docs/sandboxes/layout-dockview",
|
||||
"/packages/docs/sandboxes/nativeapp-dockview",
|
||||
"/packages/docs/sandboxes/nested-dockview",
|
||||
@ -29,4 +30,4 @@
|
||||
"/packages/docs/sandboxes/javascript/vanilla-dockview"
|
||||
],
|
||||
"node": "16"
|
||||
}
|
||||
}
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
8
.github/workflows/deploy-docs.yml
vendored
8
.github/workflows/deploy-docs.yml
vendored
@ -9,16 +9,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
uses: actions/checkout@v3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '16.x'
|
||||
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
|
||||
@ -26,7 +23,6 @@ jobs:
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- run: yarn install
|
||||
- run: lerna bootstrap
|
||||
- run: npm run build
|
||||
working-directory: packages/dockview-core
|
||||
- run: npm run build
|
||||
|
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
# might be required for sonar to work correctly
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
@ -16,7 +16,7 @@ jobs:
|
||||
with:
|
||||
node-version: '16.x'
|
||||
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
|
||||
|
@ -3,7 +3,7 @@
|
||||
"packages/*"
|
||||
],
|
||||
"useWorkspaces": true,
|
||||
"version": "1.7.4",
|
||||
"version": "1.7.6",
|
||||
"npmClient": "yarn",
|
||||
"command": {
|
||||
"publish": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-core",
|
||||
"version": "1.7.4",
|
||||
"version": "1.7.6",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support",
|
||||
"main": "./dist/cjs/index.js",
|
||||
"types": "./dist/cjs/index.d.ts",
|
||||
|
@ -20,10 +20,6 @@ describe('abstractDragHandler', () => {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
}
|
||||
})(element);
|
||||
|
||||
expect(element.classList.contains('dv-dragged')).toBeFalsy();
|
||||
@ -62,10 +58,6 @@ describe('abstractDragHandler', () => {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
//
|
||||
}
|
||||
})(element);
|
||||
|
||||
expect(iframe.style.pointerEvents).toBeFalsy();
|
||||
@ -84,4 +76,46 @@ describe('abstractDragHandler', () => {
|
||||
|
||||
handler.dispose();
|
||||
});
|
||||
|
||||
test('that the disabling of pointerEvents is restored on a premature disposal of the handler', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const element = document.createElement('div');
|
||||
const iframe = document.createElement('iframe');
|
||||
const webview = document.createElement('webview');
|
||||
const span = document.createElement('span');
|
||||
|
||||
document.body.appendChild(element);
|
||||
document.body.appendChild(iframe);
|
||||
document.body.appendChild(webview);
|
||||
document.body.appendChild(span);
|
||||
|
||||
const handler = new (class TestClass extends DragHandler {
|
||||
constructor(el: HTMLElement) {
|
||||
super(el);
|
||||
}
|
||||
|
||||
getData(): IDisposable {
|
||||
return {
|
||||
dispose: () => {
|
||||
// /
|
||||
},
|
||||
};
|
||||
}
|
||||
})(element);
|
||||
|
||||
expect(iframe.style.pointerEvents).toBeFalsy();
|
||||
expect(webview.style.pointerEvents).toBeFalsy();
|
||||
expect(span.style.pointerEvents).toBeFalsy();
|
||||
|
||||
fireEvent.dragStart(element);
|
||||
expect(iframe.style.pointerEvents).toBe('none');
|
||||
expect(webview.style.pointerEvents).toBe('none');
|
||||
expect(span.style.pointerEvents).toBeFalsy();
|
||||
|
||||
handler.dispose();
|
||||
expect(iframe.style.pointerEvents).toBe('auto');
|
||||
expect(webview.style.pointerEvents).toBe('auto');
|
||||
expect(span.style.pointerEvents).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
@ -541,6 +541,8 @@ describe('dockviewComponent', () => {
|
||||
},
|
||||
});
|
||||
|
||||
// dockview.layout(1000, 1000, true);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
|
||||
activeGroup: 'group-1',
|
||||
grid: {
|
||||
@ -1723,6 +1725,9 @@ describe('dockviewComponent', () => {
|
||||
test_tab_id: PanelTabPartTest,
|
||||
},
|
||||
});
|
||||
|
||||
dockview.layout(1000, 1000);
|
||||
|
||||
dockview.fromJSON({
|
||||
activeGroup: 'group-1',
|
||||
grid: {
|
||||
@ -1918,6 +1923,8 @@ describe('dockviewComponent', () => {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
|
||||
dockview.layout(1000, 1000);
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
dockview.fromJSON({
|
||||
@ -2023,6 +2030,8 @@ describe('dockviewComponent', () => {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
|
||||
dockview.layout(1000, 1000);
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
dockview.fromJSON({
|
||||
@ -2163,6 +2172,8 @@ describe('dockviewComponent', () => {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
|
||||
dockview.layout(1000, 1000);
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
dockview.fromJSON({
|
||||
@ -2448,4 +2459,164 @@ describe('dockviewComponent', () => {
|
||||
activeGroup: '1',
|
||||
});
|
||||
});
|
||||
|
||||
test('check dockview component is rendering to the DOM as expected', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
tabComponents: {
|
||||
test_tab_id: PanelTabPartTest,
|
||||
},
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
|
||||
dockview.layout(100, 100);
|
||||
|
||||
const panel1 = dockview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(dockview.element.querySelectorAll('.view').length).toBe(1);
|
||||
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(dockview.element.querySelectorAll('.view').length).toBe(1);
|
||||
|
||||
const panel3 = dockview.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(dockview.element.querySelectorAll('.view').length).toBe(1);
|
||||
|
||||
dockview.moveGroupOrPanel(
|
||||
panel3.group,
|
||||
panel3.group.id,
|
||||
panel3.id,
|
||||
'right'
|
||||
);
|
||||
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.element.querySelectorAll('.view').length).toBe(2);
|
||||
|
||||
dockview.moveGroupOrPanel(
|
||||
panel3.group,
|
||||
panel2.group.id,
|
||||
panel2.id,
|
||||
'bottom'
|
||||
);
|
||||
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.element.querySelectorAll('.view').length).toBe(4);
|
||||
|
||||
dockview.moveGroupOrPanel(
|
||||
panel2.group,
|
||||
panel1.group.id,
|
||||
panel1.id,
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
|
||||
expect(dockview.element.querySelectorAll('.view').length).toBe(2);
|
||||
});
|
||||
|
||||
test('that fromJSON layouts are resized to the current dimensions', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
tabComponents: {
|
||||
test_tab_id: PanelTabPartTest,
|
||||
},
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
dockview.layout(1000, 500);
|
||||
|
||||
dockview.fromJSON({
|
||||
activeGroup: 'group-1',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1', 'panel2'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel2',
|
||||
},
|
||||
size: 2000,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 2000,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
contentComponent: 'default',
|
||||
title: 'panel1',
|
||||
},
|
||||
panel2: {
|
||||
id: 'panel2',
|
||||
contentComponent: 'default',
|
||||
title: 'panel2',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
|
||||
activeGroup: 'group-1',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1', 'panel2'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel2',
|
||||
},
|
||||
size: 1000,
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
},
|
||||
height: 500,
|
||||
width: 1000,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
contentComponent: 'default',
|
||||
title: 'panel1',
|
||||
},
|
||||
panel2: {
|
||||
id: 'panel2',
|
||||
contentComponent: 'default',
|
||||
title: 'panel2',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,9 @@
|
||||
import { Emitter, Event } from '../events';
|
||||
import {
|
||||
Emitter,
|
||||
Event,
|
||||
addDisposableListener,
|
||||
addDisposableWindowListener,
|
||||
} from '../events';
|
||||
|
||||
describe('events', () => {
|
||||
describe('emitter', () => {
|
||||
@ -101,4 +106,138 @@ describe('events', () => {
|
||||
emitter3.fire(3);
|
||||
expect(value).toBe(3);
|
||||
});
|
||||
|
||||
it('addDisposableWindowListener with capture options', () => {
|
||||
const element = {
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
};
|
||||
|
||||
const handler = jest.fn();
|
||||
|
||||
const disposable = addDisposableWindowListener(
|
||||
element as any,
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.addEventListener).toHaveBeenCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
expect(element.removeEventListener).toBeCalledTimes(0);
|
||||
|
||||
disposable.dispose();
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('addDisposableWindowListener without capture options', () => {
|
||||
const element = {
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
};
|
||||
|
||||
const handler = jest.fn();
|
||||
|
||||
const disposable = addDisposableWindowListener(
|
||||
element as any,
|
||||
'mousedown',
|
||||
handler
|
||||
);
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.addEventListener).toHaveBeenCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
undefined
|
||||
);
|
||||
expect(element.removeEventListener).toBeCalledTimes(0);
|
||||
|
||||
disposable.dispose();
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
it('addDisposableListener with capture options', () => {
|
||||
const element = {
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
};
|
||||
|
||||
const handler = jest.fn();
|
||||
|
||||
const disposable = addDisposableListener(
|
||||
element as any,
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.addEventListener).toHaveBeenCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
expect(element.removeEventListener).toBeCalledTimes(0);
|
||||
|
||||
disposable.dispose();
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('addDisposableListener without capture options', () => {
|
||||
const element = {
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
};
|
||||
|
||||
const handler = jest.fn();
|
||||
|
||||
const disposable = addDisposableListener(
|
||||
element as any,
|
||||
'mousedown',
|
||||
handler
|
||||
);
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.addEventListener).toHaveBeenCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
undefined
|
||||
);
|
||||
expect(element.removeEventListener).toBeCalledTimes(0);
|
||||
|
||||
disposable.dispose();
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -18,6 +18,10 @@ class MockGridview implements IGridView {
|
||||
>().event;
|
||||
element: HTMLElement = document.createElement('div');
|
||||
|
||||
constructor() {
|
||||
this.element.className = 'mock-grid-view';
|
||||
}
|
||||
|
||||
layout(width: number, height: number): void {
|
||||
//
|
||||
}
|
||||
@ -116,4 +120,574 @@ describe('gridview', () => {
|
||||
|
||||
checkOrientationFlipsAtEachLevel((gridview as any).root as BranchNode);
|
||||
});
|
||||
|
||||
test('removeView: remove leaf from branch where branch becomes leaf and parent is root', () => {
|
||||
const gridview = new Gridview(
|
||||
false,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(3);
|
||||
|
||||
gridview.removeView([1, 0], Sizing.Distribute);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(2);
|
||||
});
|
||||
|
||||
test('removeView: remove leaf from branch where branch remains branch and parent is root', () => {
|
||||
const gridview = new Gridview(
|
||||
false,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 1]);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 333,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 333,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 334,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(4);
|
||||
|
||||
gridview.removeView([1, 0], Sizing.Distribute);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(3);
|
||||
});
|
||||
|
||||
test('removeView: remove leaf where parent is root', () => {
|
||||
const gridview = new Gridview(
|
||||
false,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(3);
|
||||
|
||||
gridview.removeView([0], Sizing.Distribute);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'VERTICAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(2);
|
||||
});
|
||||
|
||||
test('removeView: remove leaf from branch where branch becomes leaf and parent is not root', () => {
|
||||
const gridview = new Gridview(
|
||||
false,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 0]);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 250,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 250,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(4);
|
||||
|
||||
gridview.removeView([1, 0, 0], Sizing.Distribute);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(3);
|
||||
});
|
||||
|
||||
test('removeView: remove leaf from branch where branch remains branch and parent is not root', () => {
|
||||
const gridview = new Gridview(
|
||||
false,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 1]);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 166,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 166,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 168,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(5);
|
||||
|
||||
gridview.removeView([1, 0, 1], Sizing.Distribute);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 250,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 250,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(4);
|
||||
});
|
||||
|
||||
test('removeView: remove leaf where parent is root', () => {
|
||||
const gridview = new Gridview(
|
||||
false,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 1]);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 166,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 166,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 168,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(5);
|
||||
|
||||
gridview.removeView([1, 1], Sizing.Distribute);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 166,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 166,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 168,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(4);
|
||||
});
|
||||
});
|
||||
|
@ -471,6 +471,8 @@ describe('gridview', () => {
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
|
||||
gridview.fromJSON({
|
||||
grid: {
|
||||
height: 400,
|
||||
@ -528,7 +530,8 @@ describe('gridview', () => {
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
// gridview.layout(800, 400);
|
||||
gridview.layout(800, 400);
|
||||
|
||||
gridview.fromJSON({
|
||||
grid: {
|
||||
height: 400,
|
||||
@ -552,7 +555,6 @@ describe('gridview', () => {
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
// gridview.layout(800, 400, true);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(gridview.toJSON()))).toEqual({
|
||||
grid: {
|
||||
@ -587,7 +589,8 @@ describe('gridview', () => {
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
// gridview.layout(800, 400);
|
||||
gridview.layout(800, 400);
|
||||
|
||||
gridview.fromJSON({
|
||||
grid: {
|
||||
height: 400,
|
||||
@ -620,7 +623,6 @@ describe('gridview', () => {
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
// gridview.layout(800, 400, true);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(gridview.toJSON()))).toEqual({
|
||||
grid: {
|
||||
@ -664,7 +666,8 @@ describe('gridview', () => {
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
// gridview.layout(800, 400);
|
||||
gridview.layout(800, 400);
|
||||
|
||||
gridview.fromJSON({
|
||||
grid: {
|
||||
height: 400,
|
||||
@ -706,7 +709,6 @@ describe('gridview', () => {
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
// gridview.layout(800, 400, true);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(gridview.toJSON()))).toEqual({
|
||||
grid: {
|
||||
@ -759,7 +761,8 @@ describe('gridview', () => {
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
// gridview.layout(800, 400);
|
||||
gridview.layout(800, 400);
|
||||
|
||||
gridview.fromJSON({
|
||||
grid: {
|
||||
height: 400,
|
||||
@ -801,7 +804,6 @@ describe('gridview', () => {
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
// gridview.layout(800, 400, true);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(gridview.toJSON()))).toEqual({
|
||||
grid: {
|
||||
@ -854,6 +856,8 @@ describe('gridview', () => {
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
|
||||
gridview.fromJSON({
|
||||
grid: {
|
||||
height: 400,
|
||||
@ -895,7 +899,6 @@ describe('gridview', () => {
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
gridview.layout(800, 400, true);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(gridview.toJSON()))).toEqual({
|
||||
grid: {
|
||||
@ -948,7 +951,8 @@ describe('gridview', () => {
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
// gridview.layout(800, 400);
|
||||
gridview.layout(800, 400);
|
||||
|
||||
gridview.fromJSON({
|
||||
grid: {
|
||||
height: 400,
|
||||
@ -1005,7 +1009,6 @@ describe('gridview', () => {
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
// gridview.layout(800, 400, true);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(gridview.toJSON()))).toEqual({
|
||||
grid: {
|
||||
@ -1198,6 +1201,8 @@ describe('gridview', () => {
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
|
||||
gridview.fromJSON({
|
||||
grid: {
|
||||
height: 400,
|
||||
@ -1254,7 +1259,8 @@ describe('gridview', () => {
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
gridview.layout(800, 400, true);
|
||||
|
||||
// gridview.layout(800, 400, true);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(gridview.toJSON()))).toEqual({
|
||||
grid: {
|
||||
@ -1322,6 +1328,8 @@ describe('gridview', () => {
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
|
||||
gridview.fromJSON({
|
||||
grid: {
|
||||
height: 400,
|
||||
@ -1445,6 +1453,8 @@ describe('gridview', () => {
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
|
||||
gridview.fromJSON({
|
||||
grid: {
|
||||
height: 400,
|
||||
@ -1908,4 +1918,318 @@ describe('gridview', () => {
|
||||
|
||||
return disposable.dispose();
|
||||
});
|
||||
|
||||
test('that fromJSON layouts are resized to the current dimensions', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(1600, 800);
|
||||
|
||||
gridview.fromJSON({
|
||||
grid: {
|
||||
height: 400,
|
||||
width: 800,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
root: {
|
||||
type: 'branch',
|
||||
size: 400,
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 200,
|
||||
data: {
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'branch',
|
||||
size: 400,
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 250,
|
||||
data: {
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 150,
|
||||
data: {
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 200,
|
||||
data: {
|
||||
id: 'panel_4',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
|
||||
expect(JSON.parse(JSON.stringify(gridview.toJSON()))).toEqual({
|
||||
grid: {
|
||||
height: 800,
|
||||
width: 1600,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
root: {
|
||||
type: 'branch',
|
||||
size: 800,
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 400,
|
||||
data: {
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'branch',
|
||||
size: 800,
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 500,
|
||||
data: {
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 300,
|
||||
data: {
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 400,
|
||||
data: {
|
||||
id: 'panel_4',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
});
|
||||
|
||||
test('that a deep layout with fromJSON dimensions identical to the current dimensions loads', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(5000, 5000);
|
||||
|
||||
gridview.fromJSON({
|
||||
grid: {
|
||||
height: 5000,
|
||||
width: 5000,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
root: {
|
||||
type: 'branch',
|
||||
size: 5000,
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 1000,
|
||||
data: {
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'branch',
|
||||
size: 2000,
|
||||
data: [
|
||||
{
|
||||
type: 'branch',
|
||||
size: 4000,
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 1000,
|
||||
data: {
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'branch',
|
||||
size: 1000,
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 2000,
|
||||
data: {
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 2000,
|
||||
data: {
|
||||
id: 'panel_4',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 1000,
|
||||
data: {
|
||||
id: 'panel_5',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 2000,
|
||||
data: {
|
||||
id: 'panel_6',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
|
||||
expect(JSON.parse(JSON.stringify(gridview.toJSON()))).toEqual({
|
||||
grid: {
|
||||
height: 5000,
|
||||
width: 5000,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
root: {
|
||||
type: 'branch',
|
||||
size: 5000,
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 1000,
|
||||
data: {
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'branch',
|
||||
size: 2000,
|
||||
data: [
|
||||
{
|
||||
type: 'branch',
|
||||
size: 4000,
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 1000,
|
||||
data: {
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'branch',
|
||||
size: 1000,
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 2000,
|
||||
data: {
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 2000,
|
||||
data: {
|
||||
id: 'panel_4',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 1000,
|
||||
data: {
|
||||
id: 'panel_5',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 2000,
|
||||
data: {
|
||||
id: 'panel_6',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -408,4 +408,85 @@ describe('componentPaneview', () => {
|
||||
expect(panel1Spy).toHaveBeenCalledTimes(1);
|
||||
expect(panel2Spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('that fromJSON layouts are resized to the current dimensions', async () => {
|
||||
const paneview = new PaneviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
paneview.layout(400, 600);
|
||||
|
||||
paneview.fromJSON({
|
||||
size: 6,
|
||||
views: [
|
||||
{
|
||||
size: 1,
|
||||
data: {
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 1',
|
||||
},
|
||||
expanded: true,
|
||||
},
|
||||
{
|
||||
size: 2,
|
||||
data: {
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 2',
|
||||
},
|
||||
expanded: true,
|
||||
},
|
||||
{
|
||||
size: 3,
|
||||
data: {
|
||||
id: 'panel3',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 3',
|
||||
},
|
||||
expanded: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// heights slightly differ because header height isn't accounted for
|
||||
expect(JSON.parse(JSON.stringify(paneview.toJSON()))).toEqual({
|
||||
size: 600,
|
||||
views: [
|
||||
{
|
||||
size: 122,
|
||||
data: {
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 1',
|
||||
},
|
||||
expanded: true,
|
||||
minimumSize: 100,
|
||||
},
|
||||
{
|
||||
size: 122,
|
||||
data: {
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 2',
|
||||
},
|
||||
expanded: true,
|
||||
minimumSize: 100,
|
||||
},
|
||||
{
|
||||
size: 356,
|
||||
data: {
|
||||
id: 'panel3',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 3',
|
||||
},
|
||||
expanded: true,
|
||||
minimumSize: 100,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
Sizing,
|
||||
Splitview,
|
||||
} from '../../splitview/splitview';
|
||||
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
class Testview implements IView {
|
||||
private _element: HTMLElement = document.createElement('div');
|
||||
private _size = 0;
|
||||
@ -84,6 +84,8 @@ describe('splitview', () => {
|
||||
beforeEach(() => {
|
||||
container = document.createElement('div');
|
||||
container.className = 'container';
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('vertical splitview', () => {
|
||||
@ -596,4 +598,82 @@ describe('splitview', () => {
|
||||
expect(anyEvents).toBeFalsy();
|
||||
expect(container.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
test('dnd: pointer events to move sash', () => {
|
||||
const splitview = new Splitview(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: false,
|
||||
});
|
||||
splitview.layout(400, 500);
|
||||
|
||||
const view1 = new Testview(0, 1000);
|
||||
const view2 = new Testview(0, 1000);
|
||||
|
||||
splitview.addView(view1);
|
||||
splitview.addView(view2);
|
||||
|
||||
const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
|
||||
const removeEventListenerSpy = jest.spyOn(
|
||||
document,
|
||||
'removeEventListener'
|
||||
);
|
||||
|
||||
const sashElement = container
|
||||
.getElementsByClassName('sash')
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
// validate the expected state before drag
|
||||
expect([view1.size, view2.size]).toEqual([200, 200]);
|
||||
expect(sashElement).toBeTruthy();
|
||||
expect(view1.element.parentElement!.style.pointerEvents).toBe('');
|
||||
expect(view2.element.parentElement!.style.pointerEvents).toBe('');
|
||||
|
||||
// start the drag event
|
||||
fireEvent(
|
||||
sashElement,
|
||||
new MouseEvent('pointerdown', { clientX: 50, clientY: 100 })
|
||||
);
|
||||
|
||||
expect(addEventListenerSpy).toBeCalledTimes(3);
|
||||
|
||||
// during a sash drag the views should have pointer-events disabled
|
||||
expect(view1.element.parentElement!.style.pointerEvents).toBe('none');
|
||||
expect(view2.element.parentElement!.style.pointerEvents).toBe('none');
|
||||
|
||||
// expect a delta move of 70 - 50 = 20
|
||||
fireEvent(
|
||||
document,
|
||||
new MouseEvent('pointermove', { clientX: 70, clientY: 110 })
|
||||
);
|
||||
expect([view1.size, view2.size]).toEqual([220, 180]);
|
||||
|
||||
// expect a delta move of 75 - 70 = 5
|
||||
fireEvent(
|
||||
document,
|
||||
new MouseEvent('pointermove', { clientX: 75, clientY: 110 })
|
||||
);
|
||||
expect([view1.size, view2.size]).toEqual([225, 175]);
|
||||
|
||||
// end the drag event
|
||||
fireEvent(
|
||||
document,
|
||||
new MouseEvent('pointerup', { clientX: 70, clientY: 110 })
|
||||
);
|
||||
|
||||
expect(removeEventListenerSpy).toBeCalledTimes(3);
|
||||
|
||||
// expect pointer-eventes on views to be restored
|
||||
expect(view1.element.parentElement!.style.pointerEvents).toBe('');
|
||||
expect(view2.element.parentElement!.style.pointerEvents).toBe('');
|
||||
|
||||
fireEvent(
|
||||
document,
|
||||
new MouseEvent('pointermove', { clientX: 100, clientY: 100 })
|
||||
);
|
||||
// expect no additional resizes
|
||||
expect([view1.size, view2.size]).toEqual([225, 175]);
|
||||
// expect no additional document listeners
|
||||
expect(addEventListenerSpy).toBeCalledTimes(3);
|
||||
expect(removeEventListenerSpy).toBeCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
@ -330,7 +330,7 @@ describe('componentSplitview', () => {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
splitview.layout(600, 400);
|
||||
splitview.layout(400, 6);
|
||||
|
||||
splitview.fromJSON({
|
||||
views: [
|
||||
@ -535,4 +535,57 @@ describe('componentSplitview', () => {
|
||||
expect(panel1Spy).toHaveBeenCalledTimes(1);
|
||||
expect(panel2Spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('that fromJSON layouts are resized to the current dimensions', async () => {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
splitview.layout(400, 600);
|
||||
|
||||
splitview.fromJSON({
|
||||
views: [
|
||||
{
|
||||
size: 1,
|
||||
data: { id: 'panel1', component: 'testPanel' },
|
||||
snap: false,
|
||||
},
|
||||
{
|
||||
size: 2,
|
||||
data: { id: 'panel2', component: 'testPanel' },
|
||||
snap: true,
|
||||
},
|
||||
{ size: 3, data: { id: 'panel3', component: 'testPanel' } },
|
||||
],
|
||||
size: 6,
|
||||
orientation: Orientation.VERTICAL,
|
||||
activeView: 'panel1',
|
||||
});
|
||||
|
||||
expect(JSON.parse(JSON.stringify(splitview.toJSON()))).toEqual({
|
||||
views: [
|
||||
{
|
||||
size: 100,
|
||||
data: { id: 'panel1', component: 'testPanel' },
|
||||
snap: false,
|
||||
},
|
||||
{
|
||||
size: 200,
|
||||
data: { id: 'panel2', component: 'testPanel' },
|
||||
snap: true,
|
||||
},
|
||||
{
|
||||
size: 300,
|
||||
data: { id: 'panel3', component: 'testPanel' },
|
||||
snap: false,
|
||||
},
|
||||
],
|
||||
size: 600,
|
||||
orientation: Orientation.VERTICAL,
|
||||
activeView: 'panel1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -7,17 +7,20 @@ import {
|
||||
} from '../lifecycle';
|
||||
|
||||
export abstract class DragHandler extends CompositeDisposable {
|
||||
private readonly disposable = new MutableDisposable();
|
||||
private readonly dataDisposable = new MutableDisposable();
|
||||
private readonly pointerEventsDisposable = new MutableDisposable();
|
||||
|
||||
private readonly _onDragStart = new Emitter<void>();
|
||||
readonly onDragStart = this._onDragStart.event;
|
||||
|
||||
private iframes: HTMLElement[] = [];
|
||||
|
||||
constructor(protected readonly el: HTMLElement) {
|
||||
super();
|
||||
|
||||
this.addDisposables(this._onDragStart);
|
||||
this.addDisposables(
|
||||
this._onDragStart,
|
||||
this.dataDisposable,
|
||||
this.pointerEventsDisposable
|
||||
);
|
||||
|
||||
this.configure();
|
||||
}
|
||||
@ -28,19 +31,27 @@ export abstract class DragHandler extends CompositeDisposable {
|
||||
this.addDisposables(
|
||||
this._onDragStart,
|
||||
addDisposableListener(this.el, 'dragstart', (event) => {
|
||||
this.iframes = [
|
||||
const iframes = [
|
||||
...getElementsByTagName('iframe'),
|
||||
...getElementsByTagName('webview'),
|
||||
];
|
||||
|
||||
for (const iframe of this.iframes) {
|
||||
this.pointerEventsDisposable.value = {
|
||||
dispose: () => {
|
||||
for (const iframe of iframes) {
|
||||
iframe.style.pointerEvents = 'auto';
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
for (const iframe of iframes) {
|
||||
iframe.style.pointerEvents = 'none';
|
||||
}
|
||||
|
||||
this.el.classList.add('dv-dragged');
|
||||
setTimeout(() => this.el.classList.remove('dv-dragged'), 0);
|
||||
|
||||
this.disposable.value = this.getData(event.dataTransfer);
|
||||
this.dataDisposable.value = this.getData(event.dataTransfer);
|
||||
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
@ -61,12 +72,8 @@ export abstract class DragHandler extends CompositeDisposable {
|
||||
}
|
||||
}),
|
||||
addDisposableListener(this.el, 'dragend', () => {
|
||||
for (const iframe of this.iframes) {
|
||||
iframe.style.pointerEvents = 'auto';
|
||||
}
|
||||
this.iframes = [];
|
||||
|
||||
this.disposable.dispose();
|
||||
this.pointerEventsDisposable.dispose();
|
||||
this.dataDisposable.dispose();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -413,6 +413,10 @@ export class DockviewComponent
|
||||
throw new Error('root must be of type branch');
|
||||
}
|
||||
|
||||
// take note of the existing dimensions
|
||||
const width = this.width;
|
||||
const height = this.height;
|
||||
|
||||
this.gridview.deserialize(grid, {
|
||||
fromJSON: (node: ISerializedLeafNode<GroupPanelViewState>) => {
|
||||
const { id, locked, hideHeader, views, activeView } = node.data;
|
||||
@ -454,6 +458,8 @@ export class DockviewComponent
|
||||
},
|
||||
});
|
||||
|
||||
this.layout(width, height);
|
||||
|
||||
if (typeof activeGroup === 'string') {
|
||||
const panel = this.getPanel(activeGroup);
|
||||
if (panel) {
|
||||
@ -461,8 +467,6 @@ export class DockviewComponent
|
||||
}
|
||||
}
|
||||
|
||||
this.gridview.layout(this.width, this.height);
|
||||
|
||||
this._onDidLayoutFromJSON.fire();
|
||||
}
|
||||
|
||||
|
@ -162,7 +162,7 @@ export function addDisposableWindowListener<K extends keyof WindowEventMap>(
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
element.removeEventListener(type, listener);
|
||||
element.removeEventListener(type, listener, options);
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -177,7 +177,7 @@ export function addDisposableListener<K extends keyof HTMLElementEventMap>(
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
element.removeEventListener(type, listener);
|
||||
element.removeEventListener(type, listener, options);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -371,8 +371,7 @@ export class Gridview implements IDisposable {
|
||||
root,
|
||||
orientation,
|
||||
deserializer,
|
||||
orthogonalSize,
|
||||
true
|
||||
orthogonalSize
|
||||
) as BranchNode;
|
||||
}
|
||||
|
||||
@ -380,8 +379,7 @@ export class Gridview implements IDisposable {
|
||||
node: ISerializedNode,
|
||||
orientation: Orientation,
|
||||
deserializer: IViewDeserializer,
|
||||
orthogonalSize: number,
|
||||
isRoot = false
|
||||
orthogonalSize: number
|
||||
): Node {
|
||||
let result: Node;
|
||||
if (node.type === 'branch') {
|
||||
@ -398,14 +396,12 @@ export class Gridview implements IDisposable {
|
||||
} as INodeDescriptor;
|
||||
});
|
||||
|
||||
// HORIZONTAL => height=orthogonalsize width=size
|
||||
// VERTICAL => height=size width=orthogonalsize
|
||||
result = new BranchNode(
|
||||
orientation,
|
||||
this.proportionalLayout,
|
||||
this.styles,
|
||||
isRoot ? orthogonalSize : node.size,
|
||||
isRoot ? node.size : orthogonalSize,
|
||||
orthogonalSize, // <- size - flips at each depth
|
||||
node.size, // <- orthogonal size - flips at each depth
|
||||
children
|
||||
);
|
||||
} else {
|
||||
@ -678,67 +674,82 @@ export class Gridview implements IDisposable {
|
||||
throw new Error('Invalid location');
|
||||
}
|
||||
|
||||
const node = parent.children[index];
|
||||
const nodeToRemove = parent.children[index];
|
||||
|
||||
if (!(node instanceof LeafNode)) {
|
||||
if (!(nodeToRemove instanceof LeafNode)) {
|
||||
throw new Error('Invalid location');
|
||||
}
|
||||
|
||||
const view = node.view;
|
||||
node.dispose(); // dispose of node
|
||||
parent.removeChild(index, sizing);
|
||||
nodeToRemove.dispose();
|
||||
|
||||
const child = parent.removeChild(index, sizing);
|
||||
child.dispose();
|
||||
|
||||
if (parent.children.length === 0) {
|
||||
return view;
|
||||
if (parent.children.length !== 1) {
|
||||
return nodeToRemove.view;
|
||||
}
|
||||
|
||||
if (parent.children.length > 1) {
|
||||
return view;
|
||||
}
|
||||
// if the parent has only one child and we know the parent is a BranchNode we can make the tree
|
||||
// more efficiently spaced by replacing the parent BranchNode with the child.
|
||||
// if that child is a LeafNode then we simply replace the BranchNode with the child otherwise if the child
|
||||
// is a BranchNode too we should spread it's children into the grandparent.
|
||||
|
||||
// refer to the remaining child as the sibling
|
||||
const sibling = parent.children[0];
|
||||
|
||||
if (pathToParent.length === 0) {
|
||||
// parent is root
|
||||
// if the parent is root
|
||||
|
||||
if (sibling instanceof LeafNode) {
|
||||
return view;
|
||||
// if the sibling is a leaf node no action is required
|
||||
return nodeToRemove.view;
|
||||
}
|
||||
|
||||
// we must promote sibling to be the new root
|
||||
const child = parent.removeChild(0, sizing);
|
||||
child.dispose();
|
||||
// otherwise the sibling is a branch node. since the parent is the root and the root has only one child
|
||||
// which is a branch node we can just set this branch node to be the new root node
|
||||
|
||||
// for good housekeeping we'll removing the sibling from it's existing tree
|
||||
parent.removeChild(0, sizing);
|
||||
|
||||
// and set that sibling node to be root
|
||||
this.root = sibling;
|
||||
return view;
|
||||
|
||||
return nodeToRemove.view;
|
||||
}
|
||||
|
||||
// otherwise the parent is apart of a large sub-tree
|
||||
|
||||
const [grandParent, ..._] = [...pathToParent].reverse();
|
||||
const [parentIndex, ...__] = [...rest].reverse();
|
||||
|
||||
const isSiblingVisible = parent.isChildVisible(0);
|
||||
const childNode = parent.removeChild(0, sizing);
|
||||
childNode.dispose();
|
||||
|
||||
// either way we need to remove the sibling from it's existing tree
|
||||
parent.removeChild(0, sizing);
|
||||
|
||||
// note the sizes of all of the grandparents children
|
||||
const sizes = grandParent.children.map((_size, i) =>
|
||||
grandParent.getChildSize(i)
|
||||
);
|
||||
const parentNode = grandParent.removeChild(parentIndex, sizing);
|
||||
parentNode.dispose();
|
||||
|
||||
// remove the parent from the grandparent since we are moving the sibling to take the parents place
|
||||
// this parent is no longer used and can be disposed of
|
||||
grandParent.removeChild(parentIndex, sizing).dispose();
|
||||
|
||||
if (sibling instanceof BranchNode) {
|
||||
// replace the parent with the siblings children
|
||||
sizes.splice(
|
||||
parentIndex,
|
||||
1,
|
||||
...sibling.children.map((c) => c.size)
|
||||
);
|
||||
|
||||
// and add those siblings to the grandparent
|
||||
for (let i = 0; i < sibling.children.length; i++) {
|
||||
const child = sibling.children[i];
|
||||
grandParent.addChild(child, child.size, parentIndex + i);
|
||||
}
|
||||
} else {
|
||||
// otherwise create a new leaf node and add that to the grandparent
|
||||
|
||||
const newSibling = new LeafNode(
|
||||
sibling.view,
|
||||
orthogonal(sibling.orientation),
|
||||
@ -747,14 +758,19 @@ export class Gridview implements IDisposable {
|
||||
const siblingSizing = isSiblingVisible
|
||||
? sibling.orthogonalSize
|
||||
: Sizing.Invisible(sibling.orthogonalSize);
|
||||
|
||||
grandParent.addChild(newSibling, siblingSizing, parentIndex);
|
||||
}
|
||||
|
||||
// the containing node of the sibling is no longer required and can be disposed of
|
||||
sibling.dispose();
|
||||
|
||||
// resize everything
|
||||
for (let i = 0; i < sizes.length; i++) {
|
||||
grandParent.resizeChild(i, sizes[i]);
|
||||
}
|
||||
|
||||
return view;
|
||||
return nodeToRemove.view;
|
||||
}
|
||||
|
||||
public layout(width: number, height: number): void {
|
||||
|
@ -176,6 +176,10 @@ export class GridviewComponent
|
||||
|
||||
const queue: Function[] = [];
|
||||
|
||||
// take note of the existing dimensions
|
||||
const width = this.width;
|
||||
const height = this.height;
|
||||
|
||||
this.gridview.deserialize(grid, {
|
||||
fromJSON: (node) => {
|
||||
const { data } = node;
|
||||
@ -215,7 +219,7 @@ export class GridviewComponent
|
||||
},
|
||||
});
|
||||
|
||||
this.layout(this.width, this.height, true);
|
||||
this.layout(width, height);
|
||||
|
||||
queue.forEach((f) => f());
|
||||
|
||||
|
@ -360,6 +360,10 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
|
||||
|
||||
const queue: Function[] = [];
|
||||
|
||||
// take note of the existing dimensions
|
||||
const width = this.width;
|
||||
const height = this.height;
|
||||
|
||||
this.paneview = new Paneview(this.element, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
descriptor: {
|
||||
@ -437,7 +441,7 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
|
||||
},
|
||||
});
|
||||
|
||||
this.layout(this.width, this.height);
|
||||
this.layout(width, height);
|
||||
|
||||
queue.forEach((f) => f());
|
||||
|
||||
|
@ -106,6 +106,7 @@
|
||||
-webkit-user-select: none; // Safari
|
||||
-moz-user-select: none; // Firefox
|
||||
-ms-user-select: none; // IE 10 and IE 11
|
||||
touch-action: none;
|
||||
|
||||
&:active {
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
|
@ -393,7 +393,7 @@ export class Splitview {
|
||||
const sash = document.createElement('div');
|
||||
sash.className = 'sash';
|
||||
|
||||
const onStart = (event: MouseEvent) => {
|
||||
const onPointerStart = (event: PointerEvent) => {
|
||||
for (const item of this.viewItems) {
|
||||
item.enabled = false;
|
||||
}
|
||||
@ -486,13 +486,12 @@ export class Splitview {
|
||||
size: snappedViewItem.size,
|
||||
};
|
||||
}
|
||||
//
|
||||
|
||||
const mousemove = (mousemoveEvent: MouseEvent) => {
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
const current =
|
||||
this._orientation === Orientation.HORIZONTAL
|
||||
? mousemoveEvent.clientX
|
||||
: mousemoveEvent.clientY;
|
||||
? event.clientX
|
||||
: event.clientY;
|
||||
const delta = current - start;
|
||||
|
||||
this.resize(
|
||||
@ -521,24 +520,24 @@ export class Splitview {
|
||||
|
||||
this.saveProportions();
|
||||
|
||||
document.removeEventListener('mousemove', mousemove);
|
||||
document.removeEventListener('mouseup', end);
|
||||
document.removeEventListener('mouseend', end);
|
||||
document.removeEventListener('pointermove', onPointerMove);
|
||||
document.removeEventListener('pointerup', end);
|
||||
document.removeEventListener('pointercancel', end);
|
||||
|
||||
this._onDidSashEnd.fire(undefined);
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', mousemove);
|
||||
document.addEventListener('mouseup', end);
|
||||
document.addEventListener('mouseend', end);
|
||||
document.addEventListener('pointermove', onPointerMove);
|
||||
document.addEventListener('pointerup', end);
|
||||
document.addEventListener('pointercancel', end);
|
||||
};
|
||||
|
||||
sash.addEventListener('mousedown', onStart);
|
||||
sash.addEventListener('pointerdown', onPointerStart);
|
||||
|
||||
const sashItem: ISashItem = {
|
||||
container: sash,
|
||||
disposable: () => {
|
||||
sash.removeEventListener('mousedown', onStart);
|
||||
sash.removeEventListener('pointerdown', onPointerStart);
|
||||
this.sashContainer.removeChild(sash);
|
||||
},
|
||||
};
|
||||
|
@ -337,6 +337,10 @@ export class SplitviewComponent
|
||||
|
||||
const queue: Function[] = [];
|
||||
|
||||
// take note of the existing dimensions
|
||||
const width = this.width;
|
||||
const height = this.height;
|
||||
|
||||
this.splitview = new Splitview(this.element, {
|
||||
orientation,
|
||||
proportionalLayout: this.options.proportionalLayout,
|
||||
@ -387,7 +391,7 @@ export class SplitviewComponent
|
||||
},
|
||||
});
|
||||
|
||||
this.layout(this.width, this.height);
|
||||
this.layout(width, height);
|
||||
|
||||
queue.forEach((f) => f());
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview",
|
||||
"version": "1.7.4",
|
||||
"version": "1.7.6",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support",
|
||||
"main": "./dist/cjs/index.js",
|
||||
"types": "./dist/cjs/index.d.ts",
|
||||
@ -56,7 +56,7 @@
|
||||
"author": "https://github.com/mathuo",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dockview-core": "^1.7.4"
|
||||
"dockview-core": "^1.7.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
|
@ -20,7 +20,7 @@ import Link from '@docusaurus/Link';
|
||||
- Provide a default React tab implementation to allow for simple changes to tab renderer without rewritting the entire tab
|
||||
- Override the default tab in `ReactDockview` with the `defaultTabComponent` prop
|
||||
- Group controls renderer [#138](https://github.com/mathuo/dockview/pull/138)
|
||||
- Provide the `groupControlComponent` prop in `ReactDockview` to create custom control components for groups. <Link to="../../docs/components/dockview/#group-controls-panel">Go</Link>
|
||||
- Provide the `groupControlComponent` prop in `ReactDockview` to create custom control components for groups.
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
|
17
packages/docs/blog/2023-06-11-dockview-1.7.5.md
Normal file
17
packages/docs/blog/2023-06-11-dockview-1.7.5.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
slug: dockview-1.7.5-release
|
||||
title: Dockview 1.7.5
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference to docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Fix [#255](https://github.com/mathuo/dockview/issues/255)
|
||||
|
||||
## 🔥 Breaking changes
|
20
packages/docs/blog/2023-06-18-dockview-1.7.6.md
Normal file
20
packages/docs/blog/2023-06-18-dockview-1.7.6.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
slug: dockview-1.7.6-release
|
||||
title: Dockview 1.7.6
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference to docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- Touch support for resize handles [#278](https://github.com/mathuo/dockview/pull/278)
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Internal cleanup [#275](https://github.com/mathuo/dockview/pull/275)
|
||||
- iframe docs [#273](https://github.com/mathuo/dockview/pull/273)
|
||||
|
||||
## 🔥 Breaking changes
|
@ -27,6 +27,7 @@ import RenderingDockview from '@site/sandboxes/rendering-dockview/src/app';
|
||||
import DockviewExternalDnd from '@site/sandboxes/externaldnd-dockview/src/app';
|
||||
import DockviewResizeContainer from '@site/sandboxes/resizecontainer-dockview/src/app';
|
||||
import DockviewTabheight from '@site/sandboxes/tabheight-dockview/src/app';
|
||||
import DockviewWithIFrames from '@site/sandboxes/iframe-dockview/src/app';
|
||||
|
||||
import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app';
|
||||
import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simple-dockview/src/app';
|
||||
@ -740,6 +741,29 @@ api.group.api.setConstraints(...)
|
||||
<DockviewConstraints />
|
||||
</Container>
|
||||
|
||||
## iFrames
|
||||
|
||||
iFrames required special attention because of a particular behaviour in how iFrames render:
|
||||
|
||||
> Re-parenting an iFrame will reload the contents of the iFrame or the rephrase this, moving an iFrame within the DOM will cause a reload of its contents.
|
||||
|
||||
You can find many examples of discussions on this. Two reputable forums for example are linked [here](https://bugzilla.mozilla.org/show_bug.cgi?id=254144) and [here](https://github.com/whatwg/html/issues/5484).
|
||||
|
||||
The problem with iFrames and `dockview` is that when you hide or move a panel that panels DOM element may be moved within the DOM or removed from the DOM completely.
|
||||
If your panel contains an iFrame then that iFrame will reload after being re-positioned within the DOM tree and all state in that iFrame will most likely be lost.
|
||||
|
||||
`dockview` does not provide a built-in solution to this because it's too specific of a problem to include in the library.
|
||||
However the below example does show an implementation of a higher-order component `HoistedDockviewPanel`that you could use to work around this problems and make iFrames behave in `dockview`.
|
||||
|
||||
What the higher-order component is doing is to hoist the panels contents into a DOM element that is always present and then `position: absolute` that element to match the dimensions of it's linked panel.
|
||||
The visibility of these hoisted elements is then controlled through some exposed api methods to hide elements that shouldn't be currently shown.
|
||||
|
||||
You should open this example in CodeSandbox using the provided link to understand the code and make use of this implemention if required.
|
||||
|
||||
<Container sandboxId="iframe-dockview" height={600}>
|
||||
<DockviewWithIFrames />
|
||||
</Container>
|
||||
|
||||
## Events
|
||||
|
||||
A simple example showing events fired by `dockviewz that can be interacted with.
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-docs",
|
||||
"version": "1.7.4",
|
||||
"version": "1.7.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
@ -22,7 +22,7 @@
|
||||
"@minoru/react-dnd-treeview": "^3.4.3",
|
||||
"axios": "^1.3.3",
|
||||
"clsx": "^1.2.1",
|
||||
"dockview": "^1.7.4",
|
||||
"dockview": "^1.7.6",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
|
32
packages/docs/sandboxes/iframe-dockview/package.json
Normal file
32
packages/docs/sandboxes/iframe-dockview/package.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "iframe-dockview",
|
||||
"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"
|
||||
]
|
||||
}
|
44
packages/docs/sandboxes/iframe-dockview/public/index.html
Normal file
44
packages/docs/sandboxes/iframe-dockview/public/index.html
Normal file
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
|
||||
</html>
|
61
packages/docs/sandboxes/iframe-dockview/src/app.tsx
Normal file
61
packages/docs/sandboxes/iframe-dockview/src/app.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import {
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview';
|
||||
import * as React from 'react';
|
||||
import { HoistedDockviewPanel } from './hoistedDockviewPanel';
|
||||
|
||||
const components = {
|
||||
iframeComponent: HoistedDockviewPanel(
|
||||
(props: IDockviewPanelProps<{ color: string }>) => {
|
||||
return (
|
||||
<iframe
|
||||
style={{
|
||||
pointerEvents: 'none',
|
||||
border: 'none',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
src="https://dockview.dev"
|
||||
/>
|
||||
);
|
||||
}
|
||||
),
|
||||
basicComponent: () => {
|
||||
return (
|
||||
<div style={{ padding: '20px', color: 'white' }}>
|
||||
{'This panel is just a usual component '}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const App: React.FC = () => {
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'iframeComponent',
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'iframeComponent',
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'basicComponent',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DockviewReact
|
||||
components={components}
|
||||
onReady={onReady}
|
||||
className="dockview-theme-abyss"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
@ -0,0 +1,91 @@
|
||||
import { IDockviewPanelProps } from 'dockview';
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
// get absolute position of element allowing for scroll position
|
||||
function getDomNodePagePosition(domNode: HTMLElement): {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
} {
|
||||
const { left, top, width, height } = domNode.getBoundingClientRect();
|
||||
return {
|
||||
left: left + window.scrollX,
|
||||
top: top + window.scrollY,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
}
|
||||
|
||||
function toggleVisibility(element: HTMLElement, isVisible: boolean) {
|
||||
element.style.visibility = isVisible ? 'visible' : 'hidden';
|
||||
}
|
||||
|
||||
export const HoistedDockviewPanel = <T extends object>(
|
||||
DockviewPanelComponent: React.FC<IDockviewPanelProps<T>>
|
||||
) => {
|
||||
return (props: IDockviewPanelProps<T>) => {
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
const innerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const positionHoistedPanel = () => {
|
||||
if (!ref.current || !innerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { left, top, height, width } = getDomNodePagePosition(
|
||||
ref.current.parentElement! // use the parent element to determine our size
|
||||
);
|
||||
|
||||
innerRef.current.style.left = `${left}px`;
|
||||
innerRef.current.style.top = `${top}px`;
|
||||
innerRef.current.style.height = `${height}px`;
|
||||
innerRef.current.style.width = `${width}px`;
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!innerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const disposable1 = props.api.onDidVisibilityChange((event) => {
|
||||
if (!innerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
toggleVisibility(innerRef.current, event.isVisible); // subsequent checks of visibility
|
||||
});
|
||||
|
||||
const disposable2 = props.api.onDidDimensionsChange(() => {
|
||||
positionHoistedPanel();
|
||||
});
|
||||
|
||||
positionHoistedPanel();
|
||||
|
||||
return () => {
|
||||
disposable1.dispose(); // cleanup
|
||||
disposable2.dispose();
|
||||
};
|
||||
}, [props.api]);
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
{ReactDOM.createPortal(
|
||||
<div
|
||||
/** you may want to mark these elements with some kind of attribute id */
|
||||
ref={innerRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
pointerEvents: 'none', // prevent this wrapper contain stealing events
|
||||
}}
|
||||
>
|
||||
<DockviewPanelComponent {...props} />
|
||||
</div>,
|
||||
document.body // <-- you may choose to mount these 'global' elements to anywhere you see suitable
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
20
packages/docs/sandboxes/iframe-dockview/src/index.tsx
Normal file
20
packages/docs/sandboxes/iframe-dockview/src/index.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { StrictMode } from 'react';
|
||||
import * as ReactDOMClient from 'react-dom/client';
|
||||
import './styles.css';
|
||||
import 'dockview/dist/styles/dockview.css';
|
||||
|
||||
import App from './app';
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
|
||||
if (rootElement) {
|
||||
const root = ReactDOMClient.createRoot(rootElement);
|
||||
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<div className="app">
|
||||
<App />
|
||||
</div>
|
||||
</StrictMode>
|
||||
);
|
||||
}
|
15
packages/docs/sandboxes/iframe-dockview/src/styles.css
Normal file
15
packages/docs/sandboxes/iframe-dockview/src/styles.css
Normal file
@ -0,0 +1,15 @@
|
||||
body {
|
||||
margin: 0px;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.app {
|
||||
height: 100%;
|
||||
}
|
18
packages/docs/sandboxes/iframe-dockview/tsconfig.json
Normal file
18
packages/docs/sandboxes/iframe-dockview/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ import RenderingDockview from '@site/sandboxes/rendering-dockview/src/app';
|
||||
import DockviewExternalDnd from '@site/sandboxes/externaldnd-dockview/src/app';
|
||||
import DockviewResizeContainer from '@site/sandboxes/resizecontainer-dockview/src/app';
|
||||
import DockviewTabheight from '@site/sandboxes/tabheight-dockview/src/app';
|
||||
import DockviewWithIFrames from '@site/sandboxes/iframe-dockview/src/app';
|
||||
|
||||
import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app';
|
||||
import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simple-dockview/src/app';
|
||||
@ -739,6 +740,29 @@ api.group.api.setConstraints(...)
|
||||
<DockviewConstraints />
|
||||
</Container>
|
||||
|
||||
## iFrames
|
||||
|
||||
iFrames required special attention because of a particular behaviour in how iFrames render:
|
||||
|
||||
> Re-parenting an iFrame will reload the contents of the iFrame or the rephrase this, moving an iFrame within the DOM will cause a reload of its contents.
|
||||
|
||||
You can find many examples of discussions on this. Two reputable forums for example are linked [here](https://bugzilla.mozilla.org/show_bug.cgi?id=254144) and [here](https://github.com/whatwg/html/issues/5484).
|
||||
|
||||
The problem with iFrames and `dockview` is that when you hide or move a panel that panels DOM element may be moved within the DOM or removed from the DOM completely.
|
||||
If your panel contains an iFrame then that iFrame will reload after being re-positioned within the DOM tree and all state in that iFrame will most likely be lost.
|
||||
|
||||
`dockview` does not provide a built-in solution to this because it's too specific of a problem to include in the library.
|
||||
However the below example does show an implementation of a higher-order component `HoistedDockviewPanel`that you could use to work around this problems and make iFrames behave in `dockview`.
|
||||
|
||||
What the higher-order component is doing is to hoist the panels contents into a DOM element that is always present and then `position: absolute` that element to match the dimensions of it's linked panel.
|
||||
The visibility of these hoisted elements is then controlled through some exposed api methods to hide elements that shouldn't be currently shown.
|
||||
|
||||
You should open this example in CodeSandbox using the provided link to understand the code and make use of this implemention if required.
|
||||
|
||||
<Container sandboxId="iframe-dockview" height={600}>
|
||||
<DockviewWithIFrames />
|
||||
</Container>
|
||||
|
||||
## Events
|
||||
|
||||
A simple example showing events fired by `dockviewz that can be interacted with.
|
@ -1,3 +1,3 @@
|
||||
[
|
||||
"1.7.4"
|
||||
]
|
||||
"1.7.6"
|
||||
]
|
Loading…
Reference in New Issue
Block a user