Merge pull request #541 from mathuo/538-interested-in-porting-dockview-to-vuejs

feat: Vue 3 Support
This commit is contained in:
mathuo 2024-04-17 20:34:20 +01:00 committed by GitHub
commit 6ca39ba58b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
233 changed files with 11514 additions and 1406 deletions

View File

@ -7,7 +7,8 @@
"esbenp.prettier-vscode",
"redhat.vscode-yaml",
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig"
"editorconfig.editorconfig",
"vue.volar"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []

View File

@ -45,6 +45,9 @@
"@types/react-dom": "^18.2.18",
"@typescript-eslint/eslint-plugin": "^6.17.0",
"@typescript-eslint/parser": "^6.17.0",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/tsconfig": "^0.5.1",
"concurrently": "^8.2.2",
"cross-env": "^7.0.3",
"eslint": "^8.56.0",
"fs-extra": "^11.2.0",
@ -66,9 +69,16 @@
"ts-node": "^10.9.2",
"tslib": "^2.6.2",
"typedoc": "^0.25.6",
"typescript": "^5.3.3"
"typescript": "^5.3.3",
"vite": "^5.1.5",
"vue": "^3.4.21",
"vue-sfc-loader": "^0.1.0",
"vue-tsc": "^2.0.5"
},
"engines": {
"node": ">=18.0"
},
"dependencies": {
"ag-grid-vue3": "^31.1.1"
}
}

View File

@ -0,0 +1,56 @@
<div align="center">
<h1>dockview</h1>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews with ReactJS support written in TypeScript</p>
</div>
---
[![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)
[![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)
##
Please see the website: https://dockview.dev
## Features
- Serialization / deserialization with full layout management
- Support for split-views, grid-views and 'dockable' views
- Themeable and customizable
- Tab and Group docking / Drag n' Drop
- Popout Windows
- Floating Groups
- Extensive API
- Supports Shadow DOMs
- High test coverage
- Documentation website with live examples
- Transparent builds and Code Analysis
- Security at mind - verifed publishing and builds through GitHub Actions
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance).
## Quick start
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview).
```
npm install --save dockview
```
Within your project you must import or reference the stylesheet at `dockview/dist/styles/dockview.css` and attach a theme.
```css
@import '~dockview/dist/styles/dockview.css';
```
You should also attach a dockview theme to an element containing your components. For example:
```html
<body classname="dockview-theme-dark"></body>
```

View File

@ -0,0 +1,6 @@
const gulp = require('gulp');
const buildfile = require('../../scripts/build');
buildfile.init();
gulp.task('run', gulp.series(['sass']));

View File

@ -0,0 +1,34 @@
import { JestConfigWithTsJest } from 'ts-jest';
const config: JestConfigWithTsJest = {
preset: 'ts-jest',
roots: ['<rootDir>/packages/dockview-angular'],
modulePaths: ['<rootDir>/packages/dockview-angular/src'],
displayName: { name: 'dockview-angular', color: 'blue' },
rootDir: '../../',
collectCoverageFrom: [
'<rootDir>/packages/dockview-angular/src/**/*.{js,jsx,ts,tsx}',
],
setupFiles: [
// '<rootDir>/packages/dockview-angular/src/__tests__/__mocks__/resizeObserver.js',
],
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
coveragePathIgnorePatterns: ['/node_modules/'],
modulePathIgnorePatterns: [
// '<rootDir>/packages/dockview-angular/src/__tests__/__mocks__',
// '<rootDir>/packages/dockview-angular/src/__tests__/__test_utils__',
],
coverageDirectory: '<rootDir>/packages/dockview-angular/coverage/',
testResultsProcessor: 'jest-sonar-reporter',
testEnvironment: 'jsdom',
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.test.json',
},
],
},
};
export default config;

View File

@ -0,0 +1,59 @@
{
"name": "dockview-angular",
"version": "0.0.0-beta-0",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [
"splitview",
"split-view",
"gridview",
"grid-view",
"dockview",
"dock-view",
"grid",
"tabs",
"layout",
"layout manager",
"dock layout",
"dock",
"docking",
"splitter",
"drag-and-drop",
"drag",
"drop",
"react",
"react-component"
],
"homepage": "https://github.com/mathuo/dockview",
"bugs": {
"url": "https://github.com/mathuo/dockview/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/mathuo/dockview.git"
},
"license": "MIT",
"author": "https://github.com/mathuo",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/cjs/index.d.ts",
"files": [
"dist",
"README.md"
],
"scripts": {
"build": "npm run build:package && npm run build:bundles",
"build:bundles": "rollup -c",
"build:cjs": "cross-env ../../node_modules/.bin/tsc --build ./tsconfig.json --verbose --extendedDiagnostics",
"build:css": "gulp sass",
"build:esm": "cross-env ../../node_modules/.bin/tsc --build ./tsconfig.esm.json --verbose --extendedDiagnostics",
"build:package": "npm run build:cjs && npm run build:esm && npm run build:css",
"clean": "rimraf dist/ .build/ .rollup.cache/",
"prepublishOnly": "npm run rebuild && npm run test",
"rebuild": "npm run clean && npm run build",
"test": "cross-env ../../node_modules/.bin/jest --selectProjects dockview",
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage"
},
"dependencies": {
"dockview-core": "^1.10.1"
}
}

View File

@ -0,0 +1,113 @@
/* eslint-disable */
const { join } = require('path');
const typescript = require('@rollup/plugin-typescript');
const terser = require('@rollup/plugin-terser');
const postcss = require('rollup-plugin-postcss');
const nodeResolve = require('@rollup/plugin-node-resolve');
const { name, version, homepage, license } = require('./package.json');
const main = join(__dirname, './scripts/rollupEntryTarget.ts');
const mainNoStyles = join(__dirname, './src/index.ts');
const outputDir = join(__dirname, 'dist');
function outputFile(format, isMinified, withStyles) {
let filename = join(outputDir, name);
if (format !== 'umd') {
filename += `.${format}`;
}
if (isMinified) {
filename += '.min';
}
if (!withStyles) {
filename += '.noStyle';
}
return `${filename}.js`;
}
function getInput(options) {
const { withStyles } = options;
if (withStyles) {
return main;
}
return mainNoStyles;
}
function createBundle(format, options) {
const { withStyles, isMinified } = options;
const input = getInput(options);
const file = outputFile(format, isMinified, withStyles);
const external = [];
const output = {
file,
format,
sourcemap: true,
globals: {},
banner: [
`/**`,
` * ${name}`,
` * @version ${version}`,
` * @link ${homepage}`,
` * @license ${license}`,
` */`,
].join('\n'),
};
const plugins = [
nodeResolve({
include: ['node_modules/dockview-core/**'],
}),
typescript({
tsconfig: 'tsconfig.esm.json',
}),
];
if (isMinified) {
plugins.push(terser());
}
if (withStyles) {
plugins.push(postcss());
}
if (format === 'umd') {
output['name'] = name;
}
external.push('react', 'react-dom');
if (format === 'umd') {
output.globals['react'] = 'React';
output.globals['react-dom'] = 'ReactDOM';
}
return {
input,
output,
plugins,
external,
};
}
module.exports = [
// amd
createBundle('amd', { withStyles: false, isMinified: false }),
createBundle('amd', { withStyles: true, isMinified: false }),
createBundle('amd', { withStyles: false, isMinified: true }),
createBundle('amd', { withStyles: true, isMinified: true }),
// umd
createBundle('umd', { withStyles: false, isMinified: false }),
createBundle('umd', { withStyles: true, isMinified: false }),
createBundle('umd', { withStyles: false, isMinified: true }),
createBundle('umd', { withStyles: true, isMinified: true }),
// cjs
createBundle('cjs', { withStyles: true, isMinified: false }),
// esm
createBundle('esm', { withStyles: true, isMinified: false }),
createBundle('esm', { withStyles: true, isMinified: true }),
];

View File

@ -0,0 +1,2 @@
import '../dist/styles/dockview.css';
export * from '../src/index';

View File

@ -0,0 +1,5 @@
describe('empty', () => {
test('that passes', () => {
expect(true).toBeTruthy();
});
});

View File

@ -0,0 +1 @@
export * from 'dockview-core';

View File

@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "ES2020",
"moduleResolution": "node",
"target": "es6",
"outDir": "dist/esm",
"tsBuildInfoFile": ".build/tsconfig.tsbuildinfo.esm",
"jsx": "react",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["**/node_modules", "src/__tests__"]
}

View File

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist/cjs",
"tsBuildInfoFile": ".build/tsconfig.tsbuildinfo.cjs",
"jsx": "react",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["**/node_modules", "src/__tests__"]
}

View File

@ -0,0 +1,5 @@
{
"extends": ["../../typedoc.base.json"],
"entryPoints": ["src/index.ts"],
"exclude": ["**/dist/**"]
}

View File

@ -1,7 +1,7 @@
{
"name": "dockview-core",
"version": "1.12.0",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [
"splitview",
"split-view",
@ -53,4 +53,4 @@
"test": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-core",
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-core --coverage"
}
}
}

View File

@ -1,4 +1,4 @@
import * as React from 'react';
import React from 'react';
/**
* useful utility type to erase readonly signatures for testing purposes

View File

@ -2,7 +2,7 @@ import { fireEvent } from '@testing-library/dom';
import { Emitter, Event } from '../../../../events';
import { ContentContainer } from '../../../../dockview/components/panel/content';
import {
GroupPanelContentPartInitParameters,
GroupPanelPartInitParameters,
IContentRenderer,
} from '../../../../dockview/types';
import { CompositeDisposable } from '../../../../lifecycle';
@ -20,18 +20,13 @@ class TestContentRenderer
{
readonly element: HTMLElement;
readonly _onDidFocus = new Emitter<void>();
readonly _onDidBlur = new Emitter<void>();
readonly onDidFocus: Event<void> = this._onDidFocus.event;
readonly onDidBlur: Event<void> = this._onDidBlur.event;
constructor(public id: string) {
super();
this.element = document.createElement('div');
this.element.id = id;
}
init(parameters: GroupPanelContentPartInitParameters): void {
init(parameters: GroupPanelPartInitParameters): void {
//
}
@ -110,16 +105,6 @@ describe('contentContainer', () => {
expect(focus).toBe(1);
expect(blur).toBe(1);
// renderer explicitly asks for focus
contentRenderer._onDidFocus.fire();
expect(focus).toBe(2);
expect(blur).toBe(1);
// renderer explicitly looses focus
contentRenderer._onDidBlur.fire();
expect(focus).toBe(2);
expect(blur).toBe(2);
const contentRenderer2 = new TestContentRenderer('id-2');
const panel2 = {
@ -130,25 +115,19 @@ describe('contentContainer', () => {
} as Partial<IDockviewPanel>;
cut.openPanel(panel2 as IDockviewPanel);
expect(focus).toBe(2);
expect(blur).toBe(2);
// previous renderer events should no longer be attached to container
contentRenderer._onDidFocus.fire();
contentRenderer._onDidBlur.fire();
expect(focus).toBe(2);
expect(blur).toBe(2);
// expect(focus).toBe(2);
// expect(blur).toBe(1);
// new panel recieves focus
fireEvent.focus(contentRenderer2.element);
expect(focus).toBe(3);
expect(blur).toBe(2);
expect(focus).toBe(2);
expect(blur).toBe(1);
// new panel looses focus
fireEvent.blur(contentRenderer2.element);
jest.runAllTimers();
expect(focus).toBe(3);
expect(blur).toBe(3);
expect(focus).toBe(2);
expect(blur).toBe(2);
disposable.dispose();
});

View File

@ -18,6 +18,7 @@ import {
} from '../../dockview/components/titlebar/tabsContainer';
import { fromPartial } from '@total-typescript/shoehorn';
import { DockviewApi } from '../../api/component.api';
import { DockviewDndOverlayEvent } from '../../dockview/options';
class PanelContentPartTest implements IContentRenderer {
element: HTMLElement = document.createElement('div');
@ -2040,7 +2041,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 1000);
@ -2147,7 +2147,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 1000);
@ -2289,7 +2288,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 1000);
@ -2418,7 +2416,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 1000);
@ -2467,66 +2464,6 @@ describe('dockviewComponent', () => {
});
});
test('orthogonal realigment #5', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.VERTICAL,
});
dockview.layout(1000, 1000);
expect(dockview.orientation).toBe(Orientation.VERTICAL);
dockview.addPanel({
id: 'panel1',
component: 'default',
position: {
direction: 'left',
},
});
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
activeGroup: '1',
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel1'],
id: '1',
activeView: 'panel1',
},
size: 1000,
},
],
size: 1000,
},
height: 1000,
width: 1000,
orientation: Orientation.HORIZONTAL,
},
panels: {
panel1: {
id: 'panel1',
contentComponent: 'default',
title: 'panel1',
},
},
});
});
test('that a empty component has no groups', () => {
const container = document.createElement('div');
@ -2538,7 +2475,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
expect(dockview.groups.length).toBe(0);
@ -2555,7 +2491,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
expect(dockview.groups.length).toBe(0);
@ -2595,7 +2530,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
@ -2624,7 +2558,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(100, 100);
@ -2711,7 +2644,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(100, 100);
@ -2774,7 +2706,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
@ -2865,7 +2796,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -2897,7 +2827,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -2922,8 +2851,6 @@ describe('dockviewComponent', () => {
test('that external dnd events do not trigger the top-level center dnd target unless empty', () => {
const container = document.createElement('div');
const showDndOverlay = jest.fn().mockReturnValue(true);
const dockview = new DockviewComponent({
parentElement: container,
components: {
@ -2932,8 +2859,13 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
showDndOverlay: showDndOverlay,
});
let events: DockviewDndOverlayEvent[] = [];
dockview.onUnhandledDragOverEvent((e) => {
events.push(e);
e.accept();
});
dockview.layout(1000, 500);
@ -2973,13 +2905,11 @@ describe('dockviewComponent', () => {
});
fireEvent(dockview.element, eventLeft);
expect(showDndOverlay).toHaveBeenCalledWith({
nativeEvent: eventLeft,
position: 'left',
target: 'edge',
getData: getPanelData,
});
expect(showDndOverlay).toBeCalledTimes(1);
expect(events[0].nativeEvent).toBe(eventLeft);
expect(events[0].position).toBe('left');
expect(events[0].target).toBe('edge');
expect(events[0].getData).toBe(getPanelData);
expect(events.length).toBe(1);
// right
@ -2992,13 +2922,11 @@ describe('dockviewComponent', () => {
});
fireEvent(dockview.element, eventRight);
expect(showDndOverlay).toHaveBeenCalledWith({
nativeEvent: eventRight,
position: 'right',
target: 'edge',
getData: getPanelData,
});
expect(showDndOverlay).toBeCalledTimes(2);
expect(events[1].nativeEvent).toBe(eventRight);
expect(events[1].position).toBe('right');
expect(events[1].target).toBe('edge');
expect(events[1].getData).toBe(getPanelData);
expect(events.length).toBe(2);
// top
@ -3011,13 +2939,11 @@ describe('dockviewComponent', () => {
});
fireEvent(dockview.element, eventTop);
expect(showDndOverlay).toHaveBeenCalledWith({
nativeEvent: eventTop,
position: 'top',
target: 'edge',
getData: getPanelData,
});
expect(showDndOverlay).toBeCalledTimes(3);
expect(events[2].nativeEvent).toBe(eventTop);
expect(events[2].position).toBe('top');
expect(events[2].target).toBe('edge');
expect(events[2].getData).toBe(getPanelData);
expect(events.length).toBe(3);
// top
@ -3030,13 +2956,11 @@ describe('dockviewComponent', () => {
});
fireEvent(dockview.element, eventBottom);
expect(showDndOverlay).toHaveBeenCalledWith({
nativeEvent: eventBottom,
position: 'bottom',
target: 'edge',
getData: getPanelData,
});
expect(showDndOverlay).toBeCalledTimes(4);
expect(events[3].nativeEvent).toBe(eventBottom);
expect(events[3].position).toBe('bottom');
expect(events[3].target).toBe('edge');
expect(events[3].getData).toBe(getPanelData);
expect(events.length).toBe(4);
// center
@ -3050,7 +2974,7 @@ describe('dockviewComponent', () => {
fireEvent(dockview.element, eventCenter);
// expect not to be called for center
expect(showDndOverlay).toBeCalledTimes(4);
expect(events.length).toBe(4);
dockview.removePanel(panel1);
dockview.removePanel(panel2);
@ -3066,13 +2990,11 @@ describe('dockviewComponent', () => {
});
fireEvent(dockview.element, eventCenter2);
expect(showDndOverlay).toHaveBeenCalledWith({
nativeEvent: eventTop,
position: 'center',
target: 'edge',
getData: getPanelData,
});
expect(showDndOverlay).toBeCalledTimes(5);
expect(events[4].nativeEvent).toBe(eventCenter2);
expect(events[4].position).toBe('center');
expect(events[4].target).toBe('edge');
expect(events[4].getData).toBe(getPanelData);
expect(events.length).toBe(5);
});
test('that dragging a tab triggers onWillDragPanel', () => {
@ -3086,7 +3008,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3126,7 +3047,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3167,7 +3087,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3257,7 +3176,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3380,7 +3298,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
expect(dockview.disableResizing).toBeFalsy();
@ -3398,7 +3315,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
disableAutoResizing: true,
});
@ -3417,7 +3333,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3445,7 +3360,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3488,7 +3402,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3531,7 +3444,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3582,7 +3494,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3633,7 +3544,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3684,7 +3594,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3743,7 +3652,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3786,7 +3694,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3829,7 +3736,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3880,7 +3786,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3931,7 +3836,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -3982,7 +3886,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -4041,7 +3944,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -4092,7 +3994,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -4142,7 +4043,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -4193,7 +4093,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -4243,7 +4142,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -4283,7 +4181,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -4322,7 +4219,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -4362,7 +4258,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -4423,7 +4318,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -4462,7 +4356,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -4495,7 +4388,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -4568,7 +4460,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
@ -4613,7 +4504,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
panel1 = dockview.addPanel({
@ -4667,7 +4557,6 @@ describe('dockviewComponent', () => {
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
const api = new DockviewApi(dockview);

View File

@ -641,7 +641,6 @@ describe('dockviewGroupPanelModel', () => {
return {
id: 'testcomponentid',
options: {
showDndOverlay: jest.fn(),
parentElement: document.createElement('div'),
},
getPanel: jest.fn(),
@ -649,6 +648,7 @@ describe('dockviewGroupPanelModel', () => {
onDidRemovePanel: jest.fn(),
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
@ -678,6 +678,12 @@ describe('dockviewGroupPanelModel', () => {
new groupPanelMock() as DockviewGroupPanel
);
let counter = 0;
cut.onUnhandledDragOverEvent(() => {
counter++;
});
const element = container
.getElementsByClassName('content-container')
.item(0)!;
@ -690,7 +696,7 @@ describe('dockviewGroupPanelModel', () => {
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(1);
expect(counter).toBe(1);
expect(
element.getElementsByClassName('drop-target-dropzone').length
@ -702,7 +708,6 @@ describe('dockviewGroupPanelModel', () => {
return {
id: 'testcomponentid',
options: {
showDndOverlay: () => true,
parentElement: document.createElement('div'),
},
getPanel: jest.fn(),
@ -739,6 +744,10 @@ describe('dockviewGroupPanelModel', () => {
new groupPanelMock() as DockviewGroupPanel
);
cut.onUnhandledDragOverEvent((e) => {
e.accept();
});
const element = container
.getElementsByClassName('content-container')
.item(0)!;
@ -794,7 +803,6 @@ describe('dockviewGroupPanelModel', () => {
return {
id: 'testcomponentid',
options: {
showDndOverlay: jest.fn(),
parentElement: document.createElement('div'),
},
getPanel: jest.fn(),
@ -833,6 +841,12 @@ describe('dockviewGroupPanelModel', () => {
new groupPanelMock() as DockviewGroupPanel
);
let counter = 0;
cut.onUnhandledDragOverEvent(() => {
counter++;
});
cut.openPanel(new TestPanel('panel1', panelApi));
const element = container
@ -852,7 +866,7 @@ describe('dockviewGroupPanelModel', () => {
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(0);
expect(counter).toBe(0);
expect(
element.getElementsByClassName('drop-target-dropzone').length
@ -864,7 +878,6 @@ describe('dockviewGroupPanelModel', () => {
return {
id: 'testcomponentid',
options: {
showDndOverlay: jest.fn(),
parentElement: document.createElement('div'),
},
getPanel: jest.fn(),
@ -903,6 +916,12 @@ describe('dockviewGroupPanelModel', () => {
new groupPanelMock() as DockviewGroupPanel
);
let counter = 0;
cut.onUnhandledDragOverEvent(() => {
counter++;
});
cut.openPanel(new TestPanel('panel1', panelApi));
cut.openPanel(new TestPanel('panel2', panelApi));
@ -923,7 +942,7 @@ describe('dockviewGroupPanelModel', () => {
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(0);
expect(counter).toBe(0);
expect(
element.getElementsByClassName('drop-target-dropzone').length
@ -935,7 +954,6 @@ describe('dockviewGroupPanelModel', () => {
return {
id: 'testcomponentid',
options: {
showDndOverlay: jest.fn(),
parentElement: document.createElement('div'),
},
getPanel: jest.fn(),
@ -974,6 +992,12 @@ describe('dockviewGroupPanelModel', () => {
new groupPanelMock() as DockviewGroupPanel
);
let counter = 0;
cut.onUnhandledDragOverEvent(() => {
counter++;
});
cut.openPanel(new TestPanel('panel1', panelApi));
cut.openPanel(new TestPanel('panel2', panelApi));
@ -994,7 +1018,7 @@ describe('dockviewGroupPanelModel', () => {
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(1);
expect(counter).toBe(1);
expect(
element.getElementsByClassName('drop-target-dropzone').length

View File

@ -5,6 +5,7 @@ import {
import {
AddGroupOptions,
AddPanelOptions,
DockviewDndOverlayEvent,
MovementOptions,
} from '../dockview/options';
import { Parameters } from '../panel/types';
@ -695,6 +696,10 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
return this.component.onWillDragPanel;
}
get onUnhandledDragOverEvent(): Event<DockviewDndOverlayEvent> {
return this.component.onUnhandledDragOverEvent;
}
/**
* All panel objects.
*/

View File

@ -1,12 +1,17 @@
import { Position, positionToDirection } from '../dnd/droptarget';
import { DockviewComponent } from '../dockview/dockviewComponent';
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
import { DockviewGroupLocation } from '../dockview/dockviewGroupPanelModel';
import {
DockviewGroupChangeEvent,
DockviewGroupLocation,
} from '../dockview/dockviewGroupPanelModel';
import { Emitter, Event } from '../events';
import { MutableDisposable } from '../lifecycle';
import { GridviewPanelApi, GridviewPanelApiImpl } from './gridviewPanelApi';
export interface DockviewGroupPanelApi extends GridviewPanelApi {
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent>;
readonly onDidActivePanelChange: Event<DockviewGroupChangeEvent>;
readonly location: DockviewGroupLocation;
/**
* If you require the Window object
@ -27,6 +32,8 @@ export interface DockviewGroupPanelFloatingChangeEvent {
const NOT_INITIALIZED_MESSAGE = 'DockviewGroupPanelApiImpl not initialized';
export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
private readonly _mutableDisposable = new MutableDisposable();
private _group: DockviewGroupPanel | undefined;
readonly _onDidLocationChange =
@ -34,6 +41,10 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent> =
this._onDidLocationChange.event;
private readonly _onDidActivePanelChange =
new Emitter<DockviewGroupChangeEvent>();
readonly onDidActivePanelChange = this._onDidActivePanelChange.event;
get location(): DockviewGroupLocation {
if (!this._group) {
throw new Error(NOT_INITIALIZED_MESSAGE);
@ -44,7 +55,11 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
constructor(id: string, private readonly accessor: DockviewComponent) {
super(id, '__dockviewgroup__');
this.addDisposables(this._onDidLocationChange);
this.addDisposables(
this._onDidLocationChange,
this._onDidActivePanelChange,
this._mutableDisposable
);
}
close(): void {
@ -116,5 +131,19 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
initialize(group: DockviewGroupPanel): void {
this._group = group;
/**
* TODO: Annoying initialization order caveat
*
* Due to the order on initialization we know that the model isn't defined until later in the same stack-frame of setup.
* By queuing a microtask we can ensure the setup is completed within the same stack-frame, but after everything else has
* finished ensuring the `model` is defined.
*/
queueMicrotask(() => {
this._mutableDisposable.value =
this._group!.model.onDidActivePanelChange((event) => {
this._onDidActivePanelChange.fire(event);
});
});
}
}

View File

@ -68,9 +68,10 @@ export function positionToDirection(position: Position): Direction {
export type Position = 'top' | 'bottom' | 'left' | 'right' | 'center';
export type CanDisplayOverlay =
| boolean
| ((dragEvent: DragEvent, state: Position) => boolean);
export type CanDisplayOverlay = (
dragEvent: DragEvent,
state: Position
) => boolean;
export type MeasuredValue = { value: number; type: 'pixels' | 'percentage' };
@ -170,6 +171,11 @@ export class Droptarget extends CompositeDisposable {
return;
}
if (!this.options.canDisplayOverlay(e, quadrant)) {
this.removeDropTarget();
return;
}
const willShowOverlayEvent = new WillShowOverlayEvent({
nativeEvent: e,
position: quadrant,
@ -186,16 +192,6 @@ export class Droptarget extends CompositeDisposable {
return;
}
if (typeof this.options.canDisplayOverlay === 'boolean') {
if (!this.options.canDisplayOverlay) {
this.removeDropTarget();
return;
}
} else if (!this.options.canDisplayOverlay(e, quadrant)) {
this.removeDropTarget();
return;
}
this.markAsUsed(e);
if (!this.targetElement) {

View File

@ -157,9 +157,6 @@ export class ContentContainer
}
if (doRender) {
const _onDidFocus = panel.view.content.onDidFocus;
const _onDidBlur = panel.view.content.onDidBlur;
const focusTracker = trackFocus(container);
const disposable = new CompositeDisposable();
@ -169,17 +166,6 @@ export class ContentContainer
focusTracker.onDidBlur(() => this._onDidBlur.fire())
);
if (_onDidFocus) {
disposable.addDisposables(
_onDidFocus(() => this._onDidFocus.fire())
);
}
if (_onDidBlur) {
disposable.addDisposables(
_onDidBlur(() => this._onDidBlur.fire())
);
}
this.disposable.value = disposable;
}
}

View File

@ -12,7 +12,7 @@ import {
} from '../dnd/droptarget';
import { tail, sequenceEquals, remove } from '../array';
import { DockviewPanel, IDockviewPanel } from './dockviewPanel';
import { CompositeDisposable, Disposable, IDisposable } from '../lifecycle';
import { CompositeDisposable, Disposable } from '../lifecycle';
import { Event, Emitter, addDisposableWindowListener } from '../events';
import { Watermark } from './components/watermark/watermark';
import { IWatermarkRenderer, GroupviewPanelState } from './types';
@ -23,6 +23,9 @@ import {
AddGroupOptions,
AddPanelOptions,
DockviewComponentOptions,
DockviewDndOverlayEvent,
DockviewOptions,
DockviewUnhandledDragOverEvent,
isGroupOptionsWithGroup,
isGroupOptionsWithPanel,
isPanelOptionsWithGroup,
@ -233,25 +236,6 @@ function typeValidate(data: SerializedDockview): void {
typeValidate2(grid.root, '.grid.root');
}
export type DockviewComponentUpdateOptions = Pick<
DockviewComponentOptions,
| 'orientation'
| 'components'
| 'frameworkComponents'
| 'tabComponents'
| 'frameworkTabComponents'
| 'showDndOverlay'
| 'watermarkFrameworkComponent'
| 'defaultTabComponent'
| 'createLeftHeaderActionsElement'
| 'createRightHeaderActionsElement'
| 'createPrefixHeaderActionsElement'
| 'disableFloatingGroups'
| 'floatingGroupBounds'
| 'rootOverlayModel'
| 'disableDnd'
>;
type MoveGroupOptions = {
from: { group: DockviewGroupPanel };
to: { group: DockviewGroupPanel; position: Position };
@ -286,8 +270,9 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
readonly onDidRemoveGroup: Event<DockviewGroupPanel>;
readonly onDidAddGroup: Event<DockviewGroupPanel>;
readonly onDidActiveGroupChange: Event<DockviewGroupPanel | undefined>;
readonly onUnhandledDragOverEvent: Event<DockviewDndOverlayEvent>;
readonly options: DockviewComponentOptions;
updateOptions(options: DockviewComponentUpdateOptions): void;
updateOptions(options: DockviewOptions): void;
moveGroupOrPanel(options: MoveGroupOrPanelOptions): void;
moveGroup(options: MoveGroupOptions): void;
doSetGroupActive: (group: DockviewGroupPanel, skipFocus?: boolean) => void;
@ -354,6 +339,11 @@ export class DockviewComponent
readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent> =
this._onWillShowOverlay.event;
private readonly _onUnhandledDragOverEvent =
new Emitter<DockviewDndOverlayEvent>();
readonly onUnhandledDragOverEvent: Event<DockviewDndOverlayEvent> =
this._onUnhandledDragOverEvent.event;
private readonly _onDidRemovePanel = new Emitter<IDockviewPanel>();
readonly onDidRemovePanel: Event<IDockviewPanel> =
this._onDidRemovePanel.event;
@ -436,8 +426,10 @@ export class DockviewComponent
constructor(options: DockviewComponentOptions) {
super({
proportionalLayout: true,
orientation: options.orientation ?? Orientation.HORIZONTAL,
styles: options.styles,
orientation: Orientation.HORIZONTAL,
styles: options.hideBorders
? { separatorBorder: 'transparent' }
: undefined,
parentElement: options.parentElement,
disableAutoResizing: options.disableAutoResizing,
locked: options.locked,
@ -467,6 +459,7 @@ export class DockviewComponent
this._onDidAddGroup,
this._onDidRemoveGroup,
this._onDidActiveGroupChange,
this._onUnhandledDragOverEvent,
this.onDidAdd((event) => {
if (!this._moving) {
this._onDidAddGroup.fire(event);
@ -547,25 +540,25 @@ export class DockviewComponent
return true;
}
if (this.options.showDndOverlay) {
if (position === 'center' && this.gridview.length !== 0) {
/**
* for external events only show the four-corner drag overlays, disable
* the center position so that external drag events can fall through to the group
* and panel drop target handlers
*/
return false;
}
return this.options.showDndOverlay({
nativeEvent: event,
position: position,
target: 'edge',
getData: getPanelData,
});
if (position === 'center' && this.gridview.length !== 0) {
/**
* for external events only show the four-corner drag overlays, disable
* the center position so that external drag events can fall through to the group
* and panel drop target handlers
*/
return false;
}
return false;
const firedEvent = new DockviewUnhandledDragOverEvent(
event,
'edge',
position,
getPanelData
);
this._onUnhandledDragOverEvent.fire(firedEvent);
return firedEvent.isAccepted;
},
acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
overlayModel:
@ -1052,24 +1045,17 @@ export class DockviewComponent
}
}
updateOptions(options: DockviewComponentUpdateOptions): void {
const changed_orientation =
typeof options.orientation === 'string' &&
this.gridview.orientation !== options.orientation;
updateOptions(options: Partial<DockviewComponentOptions>): void {
const changed_floatingGroupBounds =
options.floatingGroupBounds !== undefined &&
'floatingGroupBounds' in options &&
options.floatingGroupBounds !== this.options.floatingGroupBounds;
const changed_rootOverlayOptions =
options.rootOverlayModel !== undefined &&
'rootOverlayModel' in options &&
options.rootOverlayModel !== this.options.rootOverlayModel;
this._options = { ...this.options, ...options };
if (changed_orientation) {
this.gridview.orientation = options.orientation!;
}
if (changed_floatingGroupBounds) {
for (const group of this._floatingGroups) {
switch (this.options.floatingGroupBounds) {
@ -2162,6 +2148,9 @@ export class DockviewComponent
this._onWillShowOverlay.fire(event);
}),
view.model.onUnhandledDragOverEvent((event) => {
this._onUnhandledDragOverEvent.fire(event);
}),
view.model.onDidAddPanel((event) => {
if (this._moving) {
return;

View File

@ -31,7 +31,11 @@ import {
import { IWatermarkRenderer } from './types';
import { DockviewGroupPanel } from './dockviewGroupPanel';
import { IDockviewPanel } from './dockviewPanel';
import { IHeaderActionsRenderer } from './options';
import {
DockviewDndOverlayEvent,
DockviewUnhandledDragOverEvent,
IHeaderActionsRenderer,
} from './options';
import { OverlayRenderContainer } from '../overlayRenderContainer';
import { TitleEvent } from '../api/dockviewPanelApi';
@ -310,6 +314,11 @@ export class DockviewGroupPanelModel
readonly onDidActivePanelChange: Event<DockviewGroupChangeEvent> =
this._onDidActivePanelChange.event;
private readonly _onUnhandledDragOverEvent =
new Emitter<DockviewDndOverlayEvent>();
readonly onUnhandledDragOverEvent: Event<DockviewDndOverlayEvent> =
this._onUnhandledDragOverEvent.event;
private readonly _api: DockviewApi;
get element(): HTMLElement {
@ -491,7 +500,8 @@ export class DockviewGroupPanelModel
this._onWillDrop,
this._onDidAddPanel,
this._onDidRemovePanel,
this._onDidActivePanelChange
this._onDidActivePanelChange,
this._onUnhandledDragOverEvent
);
}
@ -536,45 +546,48 @@ export class DockviewGroupPanelModel
this.setActive(this.isActive, true);
this.updateContainer();
if (this.accessor.options.createRightHeaderActionsElement) {
if (this.accessor.options.headerRightActionComponent) {
this._rightHeaderActions =
this.accessor.options.createRightHeaderActionsElement(
this.accessor.options.headerRightActionComponent(
this.groupPanel
);
this.addDisposables(this._rightHeaderActions);
this._rightHeaderActions.init({
containerApi: this._api,
api: this.groupPanel.api,
group: this.groupPanel,
});
this.tabsContainer.setRightActionsElement(
this._rightHeaderActions.element
);
}
if (this.accessor.options.createLeftHeaderActionsElement) {
if (this.accessor.options.headerLeftActionComponent) {
this._leftHeaderActions =
this.accessor.options.createLeftHeaderActionsElement(
this.accessor.options.headerLeftActionComponent(
this.groupPanel
);
this.addDisposables(this._leftHeaderActions);
this._leftHeaderActions.init({
containerApi: this._api,
api: this.groupPanel.api,
group: this.groupPanel,
});
this.tabsContainer.setLeftActionsElement(
this._leftHeaderActions.element
);
}
if (this.accessor.options.createPrefixHeaderActionsElement) {
if (this.accessor.options.headerPrefixActionComponent) {
this._prefixHeaderActions =
this.accessor.options.createPrefixHeaderActionsElement(
this.accessor.options.headerPrefixActionComponent(
this.groupPanel
);
this.addDisposables(this._prefixHeaderActions);
this._prefixHeaderActions.init({
containerApi: this._api,
api: this.groupPanel.api,
group: this.groupPanel,
});
this.tabsContainer.setPrefixActionsElement(
this._prefixHeaderActions.element
@ -853,10 +866,8 @@ export class DockviewGroupPanelModel
this._panels.splice(index, 1);
if (this.mostRecentlyUsed.includes(panel)) {
this.mostRecentlyUsed.splice(
this.mostRecentlyUsed.indexOf(panel),
1
);
const index = this.mostRecentlyUsed.indexOf(panel);
this.mostRecentlyUsed.splice(index, 1);
}
const disposable = this._panelDisposables.get(panel.id);
@ -977,17 +988,17 @@ export class DockviewGroupPanelModel
position: Position,
target: DockviewGroupDropLocation
): boolean {
// custom overlay handler
if (this.accessor.options.showDndOverlay) {
return this.accessor.options.showDndOverlay({
nativeEvent: event,
target,
group: this.accessor.getPanel(this.id)!,
position,
getData: getPanelData,
});
}
return false;
const firedEvent = new DockviewUnhandledDragOverEvent(
event,
target,
position,
getPanelData,
this.accessor.getPanel(this.id)!
);
this._onUnhandledDragOverEvent.fire(firedEvent);
return firedEvent.isAccepted;
}
private handleDropEvent(

View File

@ -44,7 +44,7 @@ export class DockviewPanelModel implements IDockviewPanelModel {
}
init(params: GroupPanelPartInitParameters): void {
this.content.init({ ...params, tab: this.tab });
this.content.init(params);
this.tab.init(params);
}

View File

@ -0,0 +1,43 @@
import { DockviewApi } from '../api/component.api';
import { DockviewGroupPanelApi } from '../api/dockviewGroupPanelApi';
import { DockviewPanelApi } from '../api/dockviewPanelApi';
import { PanelParameters } from '../framwork';
import { DockviewGroupPanel, IDockviewGroupPanel } from './dockviewGroupPanel';
import { IDockviewPanel } from './dockviewPanel';
export interface IGroupPanelBaseProps<T extends { [index: string]: any } = any>
extends PanelParameters<T> {
api: DockviewPanelApi;
containerApi: DockviewApi;
}
export type IDockviewPanelHeaderProps<
T extends { [index: string]: any } = any
> = IGroupPanelBaseProps<T>;
export type IDockviewPanelProps<T extends { [index: string]: any } = any> =
IGroupPanelBaseProps<T>;
export interface IDockviewHeaderActionsProps {
api: DockviewGroupPanelApi;
containerApi: DockviewApi;
panels: IDockviewPanel[];
activePanel: IDockviewPanel | undefined;
isGroupActive: boolean;
group: DockviewGroupPanel;
}
export interface IGroupHeaderProps {
api: DockviewGroupPanelApi;
containerApi: DockviewApi;
group: IDockviewGroupPanel;
}
export interface IWatermarkPanelProps {
containerApi: DockviewApi;
group?: IDockviewGroupPanel;
}
export interface DockviewReadyEvent {
api: DockviewApi;
}

View File

@ -9,25 +9,24 @@ import {
} from './types';
import { Parameters } from '../panel/types';
import { DockviewGroupPanel } from './dockviewGroupPanel';
import { ISplitviewStyles, Orientation } from '../splitview/splitview';
import { PanelTransfer } from '../dnd/dataTransfer';
import { IDisposable } from '../lifecycle';
import { DroptargetOverlayModel, Position } from '../dnd/droptarget';
import { DockviewGroupDropLocation, GroupOptions } from './dockviewGroupPanelModel';
import {
DockviewGroupDropLocation,
GroupOptions,
} from './dockviewGroupPanelModel';
import { IDockviewPanel } from './dockviewPanel';
import {
ComponentConstructor,
FrameworkFactory,
} from '../panel/componentFactory';
import { DockviewGroupPanelApi } from '../api/dockviewGroupPanelApi';
import { DockviewPanelRenderer } from '../overlayRenderContainer';
import { IGroupHeaderProps } from './framework';
export interface IHeaderActionsRenderer extends IDisposable {
readonly element: HTMLElement;
init(params: {
containerApi: DockviewApi;
api: DockviewGroupPanelApi;
}): void;
init(params: IGroupHeaderProps): void;
}
export interface GroupPanelFrameworkComponentFactory {
@ -42,54 +41,15 @@ export interface TabContextMenuEvent {
panel: IDockviewPanel;
}
export interface DockviewRenderFunctions {
tabComponents?: {
[componentName: string]: ComponentConstructor<ITabRenderer>;
};
components?: {
[componentName: string]: ComponentConstructor<IContentRenderer>;
};
frameworkTabComponents?: {
[componentName: string]: any;
};
frameworkComponents?: {
[componentName: string]: any;
};
}
export interface ViewFactoryData {
content: string;
tab?: string;
}
export interface DockviewDndOverlayEvent {
nativeEvent: DragEvent;
target: DockviewGroupDropLocation;
position: Position;
group?: DockviewGroupPanel;
getData: () => PanelTransfer | undefined;
}
export interface DockviewComponentOptions extends DockviewRenderFunctions {
export interface DockviewOptions {
disableAutoResizing?: boolean;
watermarkComponent?: WatermarkConstructor;
watermarkFrameworkComponent?: any;
frameworkComponentFactory?: GroupPanelFrameworkComponentFactory;
orientation?: Orientation;
styles?: ISplitviewStyles;
defaultTabComponent?: string;
showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean;
createRightHeaderActionsElement?: (
group: DockviewGroupPanel
) => IHeaderActionsRenderer;
createLeftHeaderActionsElement?: (
group: DockviewGroupPanel
) => IHeaderActionsRenderer;
createPrefixHeaderActionsElement?: (
group: DockviewGroupPanel
) => IHeaderActionsRenderer;
hideBorders?: boolean;
singleTabMode?: 'fullwidth' | 'default';
parentElement: HTMLElement;
disableFloatingGroups?: boolean;
floatingGroupBounds?:
| 'boundedWithinViewport'
@ -105,6 +65,91 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
disableDnd?: boolean;
}
export interface DockviewDndOverlayEvent {
nativeEvent: DragEvent;
target: DockviewGroupDropLocation;
position: Position;
group?: DockviewGroupPanel;
getData: () => PanelTransfer | undefined;
//
isAccepted: boolean;
accept(): void;
}
export class DockviewUnhandledDragOverEvent implements DockviewDndOverlayEvent {
private _isAccepted = false;
get isAccepted(): boolean {
return this._isAccepted;
}
constructor(
readonly nativeEvent: DragEvent,
readonly target: DockviewGroupDropLocation,
readonly position: Position,
readonly getData: () => PanelTransfer | undefined,
readonly group?: DockviewGroupPanel
) {}
accept(): void {
this._isAccepted = true;
}
}
export const PROPERTY_KEYS: (keyof DockviewOptions)[] = (() => {
/**
* by readong the keys from an empty value object TypeScript will error
* when we add or remove new properties to `DockviewOptions`
*/
const properties: Record<keyof DockviewOptions, undefined> = {
disableAutoResizing: undefined,
hideBorders: undefined,
singleTabMode: undefined,
disableFloatingGroups: undefined,
floatingGroupBounds: undefined,
popoutUrl: undefined,
defaultRenderer: undefined,
debug: undefined,
rootOverlayModel: undefined,
locked: undefined,
disableDnd: undefined,
};
return Object.keys(properties) as (keyof DockviewOptions)[];
})();
export interface DockviewFrameworkOptions {
headerRightActionComponent?: (
group: DockviewGroupPanel
) => IHeaderActionsRenderer;
headerLeftActionComponent?: (
group: DockviewGroupPanel
) => IHeaderActionsRenderer;
headerPrefixActionComponent?: (
group: DockviewGroupPanel
) => IHeaderActionsRenderer;
tabComponents?: {
[componentName: string]: ComponentConstructor<ITabRenderer>;
};
components?: {
[componentName: string]: ComponentConstructor<IContentRenderer>;
};
frameworkTabComponents?: {
[componentName: string]: any;
};
frameworkComponents?: {
[componentName: string]: any;
};
parentElement: HTMLElement;
defaultTabComponent?: string;
watermarkComponent?: WatermarkConstructor;
watermarkFrameworkComponent?: any;
frameworkComponentFactory?: GroupPanelFrameworkComponentFactory;
}
export type DockviewComponentOptions = DockviewOptions &
DockviewFrameworkOptions;
export interface PanelOptions<P extends object = Parameters> {
component: string;
tabComponent?: string;

View File

@ -18,11 +18,6 @@ export interface GroupPanelPartInitParameters
containerApi: DockviewApi;
}
export interface GroupPanelContentPartInitParameters
extends GroupPanelPartInitParameters {
tab: ITabRenderer;
}
export interface WatermarkRendererInitParameters {
containerApi: DockviewApi;
group?: IDockviewGroupPanel;
@ -31,7 +26,7 @@ export interface WatermarkRendererInitParameters {
export interface IWatermarkRenderer
extends Optional<
Omit<IPanel, 'id' | 'init'>,
'dispose' | 'update' | 'layout' | 'toJSON'
'dispose' | 'update' | 'layout' | 'toJSON' | 'focus'
> {
readonly element: HTMLElement;
init: (params: WatermarkRendererInitParameters) => void;
@ -41,7 +36,7 @@ export interface IWatermarkRenderer
export interface ITabRenderer
extends Optional<
Omit<IPanel, 'id'>,
'dispose' | 'update' | 'layout' | 'toJSON'
'dispose' | 'update' | 'layout' | 'toJSON' | 'focus'
> {
readonly element: HTMLElement;
init(parameters: GroupPanelPartInitParameters): void;
@ -50,12 +45,10 @@ export interface ITabRenderer
export interface IContentRenderer
extends Optional<
Omit<IPanel, 'id'>,
'dispose' | 'update' | 'layout' | 'toJSON'
'dispose' | 'update' | 'layout' | 'toJSON' | 'focus'
> {
readonly element: HTMLElement;
readonly onDidFocus?: Event<void>;
readonly onDidBlur?: Event<void>;
init(parameters: GroupPanelContentPartInitParameters): void;
init(parameters: GroupPanelPartInitParameters): void;
}
// watermark component

View File

@ -0,0 +1,6 @@
import { Parameters } from "./panel/types";
export interface PanelParameters<T extends {} = Parameters> {
params: T;
}

View File

@ -23,19 +23,28 @@ export {
export * from './paneview/paneview';
export * from './gridview/gridview';
export { GridviewComponentOptions } from './gridview/options';
export * from './dockview/dockviewGroupPanelModel';
export * from './gridview/baseComponentGridview';
export * from './paneview/draggablePaneviewPanel';
export * from './dockview/components/panel/content';
export * from './dockview/components/tab/tab';
export * from './dockview/dockviewGroupPanelModel';
export {
TabDragEvent,
GroupDragEvent,
} from './dockview/components/titlebar/tabsContainer';
export * from './dockview/types';
export * from './dockview/dockviewGroupPanel';
export {
IGroupPanelBaseProps,
IDockviewPanelHeaderProps,
IDockviewPanelProps,
IDockviewHeaderActionsProps,
IGroupHeaderProps,
IWatermarkPanelProps,
DockviewReadyEvent,
} from './dockview/framework';
export * from './dockview/options';
export * from './dockview/dockviewPanel';

View File

@ -0,0 +1,56 @@
<div align="center">
<h1>dockview</h1>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews with ReactJS support written in TypeScript</p>
</div>
---
[![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)
[![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)
##
Please see the website: https://dockview.dev
## Features
- Serialization / deserialization with full layout management
- Support for split-views, grid-views and 'dockable' views
- Themeable and customizable
- Tab and Group docking / Drag n' Drop
- Popout Windows
- Floating Groups
- Extensive API
- Supports Shadow DOMs
- High test coverage
- Documentation website with live examples
- Transparent builds and Code Analysis
- Security at mind - verifed publishing and builds through GitHub Actions
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance).
## Quick start
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview).
```
npm install --save dockview
```
Within your project you must import or reference the stylesheet at `dockview/dist/styles/dockview.css` and attach a theme.
```css
@import '~dockview/dist/styles/dockview.css';
```
You should also attach a dockview theme to an element containing your components. For example:
```html
<body classname="dockview-theme-dark"></body>
```

View File

@ -0,0 +1,6 @@
const gulp = require('gulp');
const buildfile = require('../../scripts/build');
buildfile.init();
gulp.task('run', gulp.series(['sass']));

View File

@ -0,0 +1,34 @@
import { JestConfigWithTsJest } from 'ts-jest';
const config: JestConfigWithTsJest = {
preset: 'ts-jest',
roots: ['<rootDir>/packages/dockview-react'],
modulePaths: ['<rootDir>/packages/dockview-react/src'],
displayName: { name: 'dockview-react', color: 'blue' },
rootDir: '../../',
collectCoverageFrom: [
'<rootDir>/packages/dockview-react/src/**/*.{js,jsx,ts,tsx}',
],
setupFiles: [
// '<rootDir>/packages/dockview-react/src/__tests__/__mocks__/resizeObserver.js',
],
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
coveragePathIgnorePatterns: ['/node_modules/'],
modulePathIgnorePatterns: [
// '<rootDir>/packages/dockview-react/src/__tests__/__mocks__',
// '<rootDir>/packages/dockview-react/src/__tests__/__test_utils__',
],
coverageDirectory: '<rootDir>/packages/dockview-react/coverage/',
testResultsProcessor: 'jest-sonar-reporter',
testEnvironment: 'jsdom',
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.test.json',
},
],
},
};
export default config;

View File

@ -0,0 +1,59 @@
{
"name": "dockview-react",
"version": "0.0.0-beta-0",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [
"splitview",
"split-view",
"gridview",
"grid-view",
"dockview",
"dock-view",
"grid",
"tabs",
"layout",
"layout manager",
"dock layout",
"dock",
"docking",
"splitter",
"drag-and-drop",
"drag",
"drop",
"react",
"react-component"
],
"homepage": "https://github.com/mathuo/dockview",
"bugs": {
"url": "https://github.com/mathuo/dockview/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/mathuo/dockview.git"
},
"license": "MIT",
"author": "https://github.com/mathuo",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/cjs/index.d.ts",
"files": [
"dist",
"README.md"
],
"scripts": {
"build": "npm run build:package && npm run build:bundles",
"build:bundles": "rollup -c",
"build:cjs": "cross-env ../../node_modules/.bin/tsc --build ./tsconfig.json --verbose --extendedDiagnostics",
"build:css": "gulp sass",
"build:esm": "cross-env ../../node_modules/.bin/tsc --build ./tsconfig.esm.json --verbose --extendedDiagnostics",
"build:package": "npm run build:cjs && npm run build:esm && npm run build:css",
"clean": "rimraf dist/ .build/ .rollup.cache/",
"prepublishOnly": "npm run rebuild && npm run test",
"rebuild": "npm run clean && npm run build",
"test": "cross-env ../../node_modules/.bin/jest --selectProjects dockview",
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage"
},
"dependencies": {
"dockview": "^1.10.1"
}
}

View File

@ -0,0 +1,113 @@
/* eslint-disable */
const { join } = require('path');
const typescript = require('@rollup/plugin-typescript');
const terser = require('@rollup/plugin-terser');
const postcss = require('rollup-plugin-postcss');
const nodeResolve = require('@rollup/plugin-node-resolve');
const { name, version, homepage, license } = require('./package.json');
const main = join(__dirname, './scripts/rollupEntryTarget.ts');
const mainNoStyles = join(__dirname, './src/index.ts');
const outputDir = join(__dirname, 'dist');
function outputFile(format, isMinified, withStyles) {
let filename = join(outputDir, name);
if (format !== 'umd') {
filename += `.${format}`;
}
if (isMinified) {
filename += '.min';
}
if (!withStyles) {
filename += '.noStyle';
}
return `${filename}.js`;
}
function getInput(options) {
const { withStyles } = options;
if (withStyles) {
return main;
}
return mainNoStyles;
}
function createBundle(format, options) {
const { withStyles, isMinified } = options;
const input = getInput(options);
const file = outputFile(format, isMinified, withStyles);
const external = [];
const output = {
file,
format,
sourcemap: true,
globals: {},
banner: [
`/**`,
` * ${name}`,
` * @version ${version}`,
` * @link ${homepage}`,
` * @license ${license}`,
` */`,
].join('\n'),
};
const plugins = [
nodeResolve({
include: ['node_modules/dockview-core/**'],
}),
typescript({
tsconfig: 'tsconfig.esm.json',
}),
];
if (isMinified) {
plugins.push(terser());
}
if (withStyles) {
plugins.push(postcss());
}
if (format === 'umd') {
output['name'] = name;
}
external.push('react', 'react-dom');
if (format === 'umd') {
output.globals['react'] = 'React';
output.globals['react-dom'] = 'ReactDOM';
}
return {
input,
output,
plugins,
external,
};
}
module.exports = [
// amd
createBundle('amd', { withStyles: false, isMinified: false }),
createBundle('amd', { withStyles: true, isMinified: false }),
createBundle('amd', { withStyles: false, isMinified: true }),
createBundle('amd', { withStyles: true, isMinified: true }),
// umd
createBundle('umd', { withStyles: false, isMinified: false }),
createBundle('umd', { withStyles: true, isMinified: false }),
createBundle('umd', { withStyles: false, isMinified: true }),
createBundle('umd', { withStyles: true, isMinified: true }),
// cjs
createBundle('cjs', { withStyles: true, isMinified: false }),
// esm
createBundle('esm', { withStyles: true, isMinified: false }),
createBundle('esm', { withStyles: true, isMinified: true }),
];

View File

@ -0,0 +1,2 @@
import '../dist/styles/dockview.css';
export * from '../src/index';

View File

@ -0,0 +1,5 @@
describe('empty', () => {
test('that passes', () => {
expect(true).toBeTruthy();
});
});

View File

View File

@ -0,0 +1 @@
export * from 'dockview';

View File

@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "ES2020",
"moduleResolution": "node",
"target": "es6",
"outDir": "dist/esm",
"tsBuildInfoFile": ".build/tsconfig.tsbuildinfo.esm",
"jsx": "react",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["**/node_modules", "src/__tests__"]
}

View File

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist/cjs",
"tsBuildInfoFile": ".build/tsconfig.tsbuildinfo.cjs",
"jsx": "react",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["**/node_modules", "src/__tests__"]
}

View File

@ -0,0 +1,5 @@
{
"extends": ["../../typedoc.base.json"],
"entryPoints": ["src/index.ts"],
"exclude": ["**/dist/**"]
}

View File

@ -0,0 +1,56 @@
<div align="center">
<h1>dockview</h1>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews with ReactJS support written in TypeScript</p>
</div>
---
[![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)
[![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)
##
Please see the website: https://dockview.dev
## Features
- Serialization / deserialization with full layout management
- Support for split-views, grid-views and 'dockable' views
- Themeable and customizable
- Tab and Group docking / Drag n' Drop
- Popout Windows
- Floating Groups
- Extensive API
- Supports Shadow DOMs
- High test coverage
- Documentation website with live examples
- Transparent builds and Code Analysis
- Security at mind - verifed publishing and builds through GitHub Actions
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance).
## Quick start
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview).
```
npm install --save dockview
```
Within your project you must import or reference the stylesheet at `dockview/dist/styles/dockview.css` and attach a theme.
```css
@import '~dockview/dist/styles/dockview.css';
```
You should also attach a dockview theme to an element containing your components. For example:
```html
<body classname="dockview-theme-dark"></body>
```

View File

@ -0,0 +1,6 @@
const gulp = require('gulp');
const buildfile = require('../../scripts/build');
buildfile.init();
gulp.task('run', gulp.series(['sass']));

View File

@ -0,0 +1,34 @@
import { JestConfigWithTsJest } from 'ts-jest';
const config: JestConfigWithTsJest = {
preset: 'ts-jest',
roots: ['<rootDir>/packages/dockview-vue'],
modulePaths: ['<rootDir>/packages/dockview-vue/src'],
displayName: { name: 'dockview', color: 'blue' },
rootDir: '../../',
collectCoverageFrom: [
'<rootDir>/packages/dockview-vue/src/**/*.{js,jsx,ts,tsx}',
],
setupFiles: [
// '<rootDir>/packages/dockview-vue/src/__tests__/__mocks__/resizeObserver.js',
],
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
coveragePathIgnorePatterns: ['/node_modules/'],
modulePathIgnorePatterns: [
// '<rootDir>/packages/dockview-vue/src/__tests__/__mocks__',
// '<rootDir>/packages/dockview-vue/src/__tests__/__test_utils__',
],
coverageDirectory: '<rootDir>/packages/dockview-vue/coverage/',
testResultsProcessor: 'jest-sonar-reporter',
testEnvironment: 'jsdom',
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.test.json',
},
],
},
};
export default config;

View File

@ -0,0 +1,61 @@
{
"name": "dockview-vue",
"version": "0.0.0-beta-0",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [
"splitview",
"split-view",
"gridview",
"grid-view",
"dockview",
"dock-view",
"grid",
"tabs",
"layout",
"layout manager",
"dock layout",
"dock",
"docking",
"splitter",
"drag-and-drop",
"drag",
"drop",
"react",
"react-component"
],
"main": "dist/dockview-vue.umd.js",
"module": "dist/dockview-vue.es.js",
"types": "dist/types/index.d.ts",
"homepage": "https://github.com/mathuo/dockview",
"bugs": {
"url": "https://github.com/mathuo/dockview/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/mathuo/dockview.git"
},
"license": "MIT",
"author": "https://github.com/mathuo",
"files": [
"dist",
"README.md"
],
"scripts": {
"build-only": "vite build",
"build": "npm run build:package && npm run build:bundles",
"build:bundles": "rollup -c",
"build:cjs": "cross-env ../../node_modules/.bin/vue-tsc --build ./tsconfig.json --verbose --extendedDiagnostics",
"build:css": "gulp sass",
"build:esm": "cross-env ../../node_modules/.bin/vue-tsc --build ./tsconfig.esm.json --verbose --extendedDiagnostics",
"build:package": "npm run build-only && npm run build:types",
"clean": "rimraf dist/ .build/ .rollup.cache/",
"prepublishOnly": "npm run rebuild && npm run test",
"rebuild": "npm run clean && npm run build",
"test": "cross-env ../../node_modules/.bin/jest --selectProjects dockview",
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage",
"build:types": "vue-tsc --project tsconfig.build-types.json --declaration --emitDeclarationOnly --outDir dist/types "
},
"dependencies": {
"dockview-core": "^1.10.1"
}
}

View File

@ -0,0 +1,116 @@
/* eslint-disable */
const { join } = require('path');
const typescript = require('@rollup/plugin-typescript');
const terser = require('@rollup/plugin-terser');
const postcss = require('rollup-plugin-postcss');
const nodeResolve = require('@rollup/plugin-node-resolve');
const vue = require('@vitejs/plugin-vue');
const { name, version, homepage, license } = require('./package.json');
const main = join(__dirname, './scripts/rollupEntryTarget.ts');
const mainNoStyles = join(__dirname, './src/index.ts');
const outputDir = join(__dirname, 'dist');
function outputFile(format, isMinified, withStyles) {
let filename = join(outputDir, name);
if (format !== 'umd') {
filename += `.${format}`;
}
if (isMinified) {
filename += '.min';
}
if (!withStyles) {
filename += '.noStyle';
}
return `${filename}.js`;
}
function getInput(options) {
const { withStyles } = options;
if (withStyles) {
return main;
}
return mainNoStyles;
}
function createBundle(format, options) {
const { withStyles, isMinified } = options;
const input = getInput(options);
const file = outputFile(format, isMinified, withStyles);
const external = [];
const output = {
file,
format,
sourcemap: true,
globals: {},
banner: [
`/**`,
` * ${name}`,
` * @version ${version}`,
` * @link ${homepage}`,
` * @license ${license}`,
` */`,
].join('\n'),
};
const plugins = [
nodeResolve({
include: ['node_modules/dockview-core/**'],
}),
typescript({
tsconfig: 'tsconfig.config.json',
}),
vue({}),
];
if (isMinified) {
plugins.push(terser());
}
if (withStyles) {
plugins.push(postcss());
}
if (format === 'umd') {
output['name'] = name;
}
external.push('react', 'react-dom');
if (format === 'umd') {
output.globals['react'] = 'React';
output.globals['react-dom'] = 'ReactDOM';
}
return {
input,
output,
plugins,
external,
};
}
module.exports = [
// amd
createBundle('amd', { withStyles: false, isMinified: false }),
createBundle('amd', { withStyles: true, isMinified: false }),
createBundle('amd', { withStyles: false, isMinified: true }),
createBundle('amd', { withStyles: true, isMinified: true }),
// umd
createBundle('umd', { withStyles: false, isMinified: false }),
createBundle('umd', { withStyles: true, isMinified: false }),
createBundle('umd', { withStyles: false, isMinified: true }),
createBundle('umd', { withStyles: true, isMinified: true }),
// cjs
createBundle('cjs', { withStyles: true, isMinified: false }),
// esm
createBundle('esm', { withStyles: true, isMinified: false }),
createBundle('esm', { withStyles: true, isMinified: true }),
];

View File

@ -0,0 +1,2 @@
import '../dist/styles/dockview.css';
export * from '../src/index';

View File

@ -0,0 +1,5 @@
describe('empty', () => {
test('that passes', () => {
expect(true).toBeTruthy();
});
});

View File

@ -0,0 +1,316 @@
<script setup lang="ts">
import {
DockviewApi,
DockviewComponent,
type IContentRenderer,
type ITabRenderer,
type IWatermarkRenderer,
type IDockviewPanelProps,
type IDockviewPanelHeaderProps,
type IGroupPanelBaseProps,
type IWatermarkPanelProps,
type DockviewOptions,
PROPERTY_KEYS,
type DockviewFrameworkOptions,
type DockviewReadyEvent,
} from 'dockview-core';
import {
ref,
onMounted,
defineProps,
defineEmits,
watch,
onBeforeUnmount,
markRaw,
toRaw,
getCurrentInstance,
} from 'vue';
import {
VueContentRenderer,
VueHeaderActionsRenderer,
VueTabRenderer,
VueWatermarkRenderer,
type VueComponent,
} from '../utils';
interface VueProps {
components: Record<string, VueComponent<IDockviewPanelProps>>;
tabComponents?: Record<string, VueComponent<IDockviewPanelHeaderProps>>;
watermarkComponent?: VueComponent<IWatermarkPanelProps>;
defaultTabComponent?: VueComponent<IDockviewPanelHeaderProps>;
rightHeaderActionsComponent?: VueComponent<IGroupPanelBaseProps>;
leftHeaderActionsComponent?: VueComponent<IGroupPanelBaseProps>;
prefixHeaderActionsComponent?: VueComponent<IGroupPanelBaseProps>;
}
const VUE_PROPERTIES = (() => {
const _value: Record<keyof VueProps, undefined> = {
components: undefined,
tabComponents: undefined,
watermarkComponent: undefined,
defaultTabComponent: undefined,
rightHeaderActionsComponent: undefined,
leftHeaderActionsComponent: undefined,
prefixHeaderActionsComponent: undefined,
};
return Object.keys(_value) as (keyof VueProps)[];
})();
type VueEvents = {
ready: [event: DockviewReadyEvent];
};
const DEFAULT_REACT_TAB = 'props.defaultTabComponent';
export type IDockviewVueProps = DockviewOptions & VueProps;
function extractCoreOptions(props: IDockviewVueProps): DockviewOptions {
const coreOptions = (PROPERTY_KEYS as (keyof DockviewOptions)[]).reduce(
(obj, key) => {
(obj as any)[key] = props[key];
return obj;
},
{} as Partial<DockviewOptions>
);
return coreOptions as DockviewOptions;
}
const emit = defineEmits<VueEvents>();
/**
* Anything here that is a Vue.js component should not be reactive
* i.e. markRaw(toRaw(...))
*/
const props = defineProps<IDockviewVueProps>();
const el = ref<HTMLElement | null>(null);
const instance = ref<DockviewComponent | null>(null);
PROPERTY_KEYS.forEach((coreOptionKey) => {
watch(
() => props[coreOptionKey],
(newValue, oldValue) => {
if (instance.value) {
instance.value.updateOptions({ [coreOptionKey]: newValue });
}
}
);
});
watch(
() => props.components,
(newValue, oldValue) => {
if (instance.value) {
instance.value.updateOptions({ frameworkComponents: newValue });
}
}
);
watch(
() => [props.tabComponents, props.defaultTabComponent],
([newTabComponents, newDefaultTabComponent], oldValue) => {
if (instance.value) {
const frameworkTabComponents = newTabComponents ?? {};
if (newDefaultTabComponent) {
frameworkTabComponents[DEFAULT_REACT_TAB] =
newDefaultTabComponent;
}
instance.value.updateOptions({
defaultTabComponent: newDefaultTabComponent
? DEFAULT_REACT_TAB
: undefined,
frameworkTabComponents,
});
}
}
);
watch(
() => props.watermarkComponent,
(newValue, oldValue) => {
if (instance.value) {
instance.value.updateOptions({
watermarkFrameworkComponent: newValue,
});
}
}
);
watch(
() => props.leftHeaderActionsComponent,
(newValue, oldValue) => {
if (instance.value) {
instance.value.updateOptions({
headerLeftActionComponent: newValue
? (group) => {
return new VueHeaderActionsRenderer(
newValue as VueComponent,
getCurrentInstance()!,
group
);
}
: undefined,
});
}
}
);
watch(
() => props.rightHeaderActionsComponent,
(newValue, oldValue) => {
if (instance.value) {
instance.value.updateOptions({
headerRightActionComponent: newValue
? (group) => {
return new VueHeaderActionsRenderer(
newValue as VueComponent,
getCurrentInstance()!,
group
);
}
: undefined,
});
}
}
);
watch(
() => props.prefixHeaderActionsComponent,
(newValue, oldValue) => {
if (instance.value) {
instance.value.updateOptions({
headerPrefixActionComponent: newValue
? (group) => {
return new VueHeaderActionsRenderer(
newValue as VueComponent,getCurrentInstance()!,
group
);
}
: undefined,
});
}
}
);
onMounted(() => {
if (!el.value) {
throw new Error('element is not mounted');
}
const frameworkTabComponents = props.tabComponents ?? {};
if (props.defaultTabComponent) {
frameworkTabComponents[DEFAULT_REACT_TAB] = props.defaultTabComponent;
}
const frameworkOptions: DockviewFrameworkOptions = {
parentElement: el.value,
frameworkComponentFactory: {
content: {
createComponent: (
id: string,
componentId: string,
component: any
): IContentRenderer => {
return new VueContentRenderer(component,getCurrentInstance()!);
},
},
tab: {
createComponent: (
id: string,
componentId: string,
component: any
): ITabRenderer => {
return new VueTabRenderer(component,getCurrentInstance()!);
},
},
watermark: {
createComponent: (
id: string,
componentId: string,
component: any
): IWatermarkRenderer => {
return new VueWatermarkRenderer(component,getCurrentInstance()!);
},
},
// action: {
// createComponent: (id: string, componentId: string, component: any): IWatermarkRenderer => {
// return new VueHeaderActionRenderer(component)
// }
// }
},
frameworkComponents: props.components,
frameworkTabComponents,
headerLeftActionComponent: props.leftHeaderActionsComponent
? (group) => {
return new VueHeaderActionsRenderer(
props.leftHeaderActionsComponent as VueComponent,getCurrentInstance()!,
group
);
}
: undefined,
headerPrefixActionComponent: props.prefixHeaderActionsComponent
? (group) => {
return new VueHeaderActionsRenderer(
props.prefixHeaderActionsComponent as VueComponent,getCurrentInstance()!,
group
);
}
: undefined,
headerRightActionComponent: props.rightHeaderActionsComponent
? (group) => {
return new VueHeaderActionsRenderer(
props.rightHeaderActionsComponent as VueComponent,getCurrentInstance()!,
group
);
}
: undefined,
defaultTabComponent: props.defaultTabComponent
? DEFAULT_REACT_TAB
: undefined,
watermarkFrameworkComponent:props.watermarkComponent
};
const dockview = new DockviewComponent({
...extractCoreOptions(props),
...frameworkOptions,
});
const { clientWidth, clientHeight } = el.value;
dockview.layout(clientWidth, clientHeight);
/**
* !!! THIS IS VERY IMPORTANT
*
* Since we store a reference to `DockviewComponent` within the Vue.js world Vue.js will 'deeply Proxyify' the object
* since this is how Vue.js does its reactivity magic.
*
* We do not want Vue.js to touch the `DockviewComponent` reference since it does not need to be reactive in accordance
* to the Vue.js reactivity model and since `DockviewComponent` is written in plain TypeScript allowing Vue.js
* to proxify the reference will cause all kinds of unexpected issues
*
* @see https://vuejs.org/guide/extras/reactivity-in-depth.html
* @see https://vuejs.org/api/reactivity-advanced.html#markraw
*/
instance.value = markRaw(dockview);
emit('ready', { api: new DockviewApi(dockview) });
});
onBeforeUnmount(() => {
if (instance.value) {
instance.value.dispose();
}
});
</script>
<template>
<div ref="el" />
</template>

View File

@ -0,0 +1,5 @@
export * from 'dockview-core';
import DockviewVue from './dockview/dockview.vue';
export { DockviewVue };
export * from './dockview/dockview.vue';

View File

@ -0,0 +1,255 @@
import type {
DockviewGroupPanel,
GroupPanelPartInitParameters,
IContentRenderer,
IGroupHeaderProps,
IHeaderActionsRenderer,
ITabRenderer,
IWatermarkRenderer,
PanelUpdateEvent,
Parameters,
WatermarkRendererInitParameters,
} from 'dockview-core';
import {
createVNode,
type ComponentOptionsBase,
render,
cloneVNode,
mergeProps,
type DefineComponent,
type ComponentInternalInstance,
} from 'vue';
export type ComponentInterface = ComponentOptionsBase<
any,
any,
any,
any,
any,
any,
any,
any
>;
export type VueComponent<T = any> = DefineComponent<T>;
/**
* TODO List
*
* 1. handle vue context-ish stuff (appContext? provides?)
*
*
*
* @see https://vuejs.org/api/render-function.html#clonevnode
* @see https://vuejs.org/api/render-function.html#mergeprops
*/
export function mountVueComponent<T extends Record<string, any>>(
component: VueComponent<T>,
parent: ComponentInternalInstance,
props: T,
element: HTMLElement
) {
let vNode = createVNode(component, Object.freeze(props));
vNode.appContext = parent.appContext;
render(vNode, element);
let runningProps = props;
return {
update: (newProps: any) => {
runningProps = { ...props, newProps };
vNode = cloneVNode(vNode, Object.freeze(runningProps));
render(vNode, element);
},
dispose: () => {
render(null, element);
},
};
}
export class VueContentRenderer implements IContentRenderer {
private _element: HTMLElement;
private _renderDisposable:
| { update: (props: any) => void; dispose: () => void }
| undefined;
get element(): HTMLElement {
return this._element;
}
constructor(
private readonly component: VueComponent,
private readonly parent: ComponentInternalInstance
) {
this._element = document.createElement('div');
this.element.className = 'dv-vue-part';
this.element.style.height = '100%';
this.element.style.width = '100%';
}
init(parameters: GroupPanelPartInitParameters): void {
const props = {
params: parameters.params,
api: parameters.api,
containerApi: parameters.containerApi,
};
this._renderDisposable?.dispose();
this._renderDisposable = mountVueComponent(
this.component,
this.parent,
props,
this.element
);
}
update(event: PanelUpdateEvent<Parameters>): void {
const params = event.params;
// TODO: handle prop updates somehow?
this._renderDisposable?.update(params);
}
focus(): void {
// TODO: make optional on interface
}
dispose(): void {
this._renderDisposable?.dispose();
}
}
export class VueTabRenderer implements ITabRenderer {
private _element: HTMLElement;
private _renderDisposable:
| { update: (props: any) => void; dispose: () => void }
| undefined;
get element(): HTMLElement {
return this._element;
}
constructor(
private readonly component: VueComponent,
private readonly parent: ComponentInternalInstance
) {
this._element = document.createElement('div');
this.element.className = 'dv-vue-part';
this.element.style.height = '100%';
this.element.style.width = '100%';
}
init(parameters: GroupPanelPartInitParameters): void {
const props = {
params: parameters.params,
api: parameters.api,
containerApi: parameters.containerApi,
};
this._renderDisposable?.dispose();
this._renderDisposable = mountVueComponent(
this.component,
this.parent,
props,
this.element
);
}
update(event: PanelUpdateEvent<Parameters>): void {
const params = event.params;
// TODO: handle prop updates somehow?
this._renderDisposable?.update(params);
}
dispose(): void {
this._renderDisposable?.dispose();
}
}
export class VueWatermarkRenderer implements IWatermarkRenderer {
private _element: HTMLElement;
private _renderDisposable:
| { update: (props: any) => void; dispose: () => void }
| undefined;
get element(): HTMLElement {
return this._element;
}
constructor(
private readonly component: VueComponent,
private readonly parent: ComponentInternalInstance
) {
this._element = document.createElement('div');
this.element.className = 'dv-vue-part';
this.element.style.height = '100%';
this.element.style.width = '100%';
}
init(parameters: WatermarkRendererInitParameters): void {
const props = {
group: parameters.group,
containerApi: parameters.containerApi,
};
this._renderDisposable?.dispose();
this._renderDisposable = mountVueComponent(
this.component,
this.parent,
props,
this.element
);
}
updateParentGroup(group: DockviewGroupPanel, visible: boolean): void {
// TODO: make optional on interface
}
update(event: PanelUpdateEvent<Parameters>): void {
const params = event.params;
// TODO: handle prop updates somehow?
this._renderDisposable?.update(params);
}
dispose(): void {
this._renderDisposable?.dispose();
}
}
export class VueHeaderActionsRenderer implements IHeaderActionsRenderer {
private _element: HTMLElement;
private _renderDisposable:
| { update: (props: any) => void; dispose: () => void }
| undefined;
get element(): HTMLElement {
return this._element;
}
constructor(
private readonly component: VueComponent,
private readonly parent: ComponentInternalInstance,
group: DockviewGroupPanel
) {
this._element = document.createElement('div');
this.element.className = 'dv-vue-header-action-part';
this._element.style.width = '100%';
this._element.style.height = '100%';
}
init(params: IGroupHeaderProps): void {
console.log(params);
this._renderDisposable?.dispose();
this._renderDisposable = mountVueComponent(
this.component,
this.parent,
{ ...params },
this.element
);
}
dispose(): void {
this._renderDisposable?.dispose();
}
}

View File

@ -0,0 +1,10 @@
// tsconfig.app.json
{
"extends": "@vue/tsconfig/tsconfig.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*", "src/**/*.cy.ts"],
"compilerOptions": {
"composite": true,
"baseUrl": "."
}
}

View File

@ -0,0 +1,10 @@
// tsconfig.build-types.json
{
"include": ["src/**/*"],
"exclude": [
"node_modules",
"**/*.cy.ts",
"**/*.spec.ts",
"**/__tests__/**/*"
]
}

View File

@ -0,0 +1,9 @@
// tsconfig.config.json
{
"extends": "@vue/tsconfig/tsconfig.json",
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
"compilerOptions": {
"composite": true,
"types": ["node"]
}
}

View File

@ -0,0 +1,5 @@
// tsconfig.json
{
"files": [],
"references": [{ "path": "./tsconfig.config.json" }, { "path": "./tsconfig.app.json" }]
}

View File

@ -0,0 +1,5 @@
{
"extends": ["../../typedoc.base.json"],
"entryPoints": ["src/index.ts"],
"exclude": ["**/dist/**"]
}

View File

@ -0,0 +1,30 @@
import { defineConfig } from 'vite';
import { resolve } from 'path';
import vue from '@vitejs/plugin-vue';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
build: {
lib: {
// src/indext.ts is where we have exported the component(s)
entry: resolve(__dirname, 'src/index.ts'),
name: 'dockview-vue',
// the name of the output files when the build is run
fileName: (format) => `dockview-vue.${format}.js`,
},
rollupOptions: {
// make sure to externalize deps that shouldn't be bundled
// into your library
external: ['vue', 'dockview-core'],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
vue: 'Vue',
['dockview-core']: 'DockviewCore',
},
},
},
},
});

View File

@ -1,7 +1,7 @@
{
"name": "dockview",
"version": "1.12.0",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [
"splitview",
"split-view",
@ -56,4 +56,4 @@
"dependencies": {
"dockview-core": "^1.12.0"
}
}
}

View File

@ -1,16 +1,19 @@
import React from 'react';
import { act, render, waitFor } from '@testing-library/react';
import { DockviewApi, IDockviewPanel } from 'dockview-core';
import {
IDockviewPanelProps,
DockviewReact,
DockviewApi,
DockviewReadyEvent,
} from '../../dockview/dockview';
import { PanelCollection } from '../../types';
IDockviewPanel,
IDockviewPanelProps,
} from 'dockview-core';
import { DockviewReact } from '../../dockview/dockview';
import { setMockRefElement } from '../__test_utils__/utils';
describe('gridview react', () => {
let components: PanelCollection<IDockviewPanelProps>;
let components: Record<
string,
React.FunctionComponent<IDockviewPanelProps>
>;
beforeEach(() => {
components = {

View File

@ -6,11 +6,13 @@ import {
GridviewReact,
GridviewReadyEvent,
} from '../../gridview/gridview';
import { PanelCollection } from '../../types';
import { setMockRefElement } from '../__test_utils__/utils';
describe('gridview react', () => {
let components: PanelCollection<IGridviewPanelProps>;
let components: Record<
string,
React.FunctionComponent<IGridviewPanelProps>
>
beforeEach(() => {
components = {

View File

@ -6,11 +6,13 @@ import {
PaneviewReact,
PaneviewReadyEvent,
} from '../../paneview/paneview';
import { PanelCollection } from '../../types';
import { setMockRefElement } from '../__test_utils__/utils';
describe('gridview react', () => {
let components: PanelCollection<IPaneviewPanelProps>;
let components: Record<
string,
React.FunctionComponent<IPaneviewPanelProps>
>;
beforeEach(() => {
components = {

View File

@ -6,11 +6,13 @@ import {
SplitviewReact,
SplitviewReadyEvent,
} from '../../splitview/splitview';
import { PanelCollection } from '../../types';
import { setMockRefElement } from '../__test_utils__/utils';
describe('splitview react', () => {
let components: PanelCollection<ISplitviewPanelProps>;
let components: Record<
string,
React.FunctionComponent<ISplitviewPanelProps>
>;
beforeEach(() => {
components = {

View File

@ -1,6 +1,6 @@
import React from 'react';
import { IDockviewPanelHeaderProps } from './dockview';
import { CloseButton } from '../svg';
import { IDockviewPanelHeaderProps } from 'dockview-core';
export type IDockviewDefaultTabProps = IDockviewPanelHeaderProps &
React.DOMAttributes<HTMLDivElement> & {

View File

@ -2,28 +2,31 @@ import React from 'react';
import {
DockviewComponent,
DockviewWillDropEvent,
DockviewDndOverlayEvent,
GroupPanelFrameworkComponentFactory,
DockviewPanelApi,
DockviewApi,
IContentRenderer,
ITabRenderer,
DockviewGroupPanel,
IHeaderActionsRenderer,
DockviewPanelRenderer,
DroptargetOverlayModel,
DockviewDidDropEvent,
IWatermarkPanelProps,
IDockviewHeaderActionsProps,
IDockviewPanelHeaderProps,
IDockviewPanelProps,
DockviewOptions,
PROPERTY_KEYS,
DockviewComponentOptions,
DockviewFrameworkOptions,
IDockviewDisposable,
DockviewDndOverlayEvent,
DockviewReadyEvent,
} from 'dockview-core';
import { ReactPanelContentPart } from './reactContentPart';
import { ReactPanelHeaderPart } from './reactHeaderPart';
import { ReactPortalStore, usePortalsLifecycle } from '../react';
import { IWatermarkPanelProps, ReactWatermarkPart } from './reactWatermarkPart';
import { PanelCollection, PanelParameters } from '../types';
import {
IDockviewHeaderActionsProps,
ReactHeaderActionsRendererPart,
} from './headerActionsRenderer';
import { ReactWatermarkPart } from './reactWatermarkPart';
import { ReactHeaderActionsRendererPart } from './headerActionsRenderer';
function createGroupControlElement(
component: React.FunctionComponent<IDockviewHeaderActionsProps> | undefined,
@ -40,54 +43,81 @@ function createGroupControlElement(
: undefined;
}
export interface IGroupPanelBaseProps<T extends { [index: string]: any } = any>
extends PanelParameters<T> {
api: DockviewPanelApi;
containerApi: DockviewApi;
}
const DEFAULT_REACT_TAB = 'props.defaultTabComponent';
export type IDockviewPanelHeaderProps<
T extends { [index: string]: any } = any
> = IGroupPanelBaseProps<T>;
export type IDockviewPanelProps<T extends { [index: string]: any } = any> =
IGroupPanelBaseProps<T>;
export interface DockviewReadyEvent {
api: DockviewApi;
}
export interface IDockviewReactProps {
onReady: (event: DockviewReadyEvent) => void;
components: PanelCollection<IDockviewPanelProps>;
tabComponents?: PanelCollection<IDockviewPanelHeaderProps>;
watermarkComponent?: React.FunctionComponent<IWatermarkPanelProps>;
onDidDrop?: (event: DockviewDidDropEvent) => void;
onWillDrop?: (event: DockviewWillDropEvent) => void;
showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean;
hideBorders?: boolean;
export interface IDockviewReactProps extends DockviewOptions {
className?: string;
disableAutoResizing?: boolean;
tabComponents?: Record<
string,
React.FunctionComponent<IDockviewPanelHeaderProps>
>;
components: Record<string, React.FunctionComponent<IDockviewPanelProps>>;
watermarkComponent?: React.FunctionComponent<IWatermarkPanelProps>;
defaultTabComponent?: React.FunctionComponent<IDockviewPanelHeaderProps>;
rightHeaderActionsComponent?: React.FunctionComponent<IDockviewHeaderActionsProps>;
leftHeaderActionsComponent?: React.FunctionComponent<IDockviewHeaderActionsProps>;
prefixHeaderActionsComponent?: React.FunctionComponent<IDockviewHeaderActionsProps>;
singleTabMode?: 'fullwidth' | 'default';
disableFloatingGroups?: boolean;
floatingGroupBounds?:
| 'boundedWithinViewport'
| {
minimumHeightWithinViewport?: number;
minimumWidthWithinViewport?: number;
};
debug?: boolean;
defaultRenderer?: DockviewPanelRenderer;
rootOverlayModel?: DroptargetOverlayModel;
locked?: boolean;
disableDnd?: boolean;
//
onReady: (event: DockviewReadyEvent) => void;
onDidDrop?: (event: DockviewDidDropEvent) => void;
onWillDrop?: (event: DockviewWillDropEvent) => void;
/**
* @deprecated use `api.onUnhandledDragOverEvent` instead. This will be removed in the next release.
*/
showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean;
}
const DEFAULT_REACT_TAB = 'props.defaultTabComponent';
function extractCoreOptions(props: IDockviewReactProps): DockviewOptions {
const coreOptions = (PROPERTY_KEYS as (keyof DockviewOptions)[]).reduce(
(obj, key) => {
obj[key] = props[key] as any;
return obj;
},
{} as Partial<DockviewComponentOptions>
);
return coreOptions as DockviewOptions;
}
function createFrameworkFactory(
addPortal: (portal: React.ReactPortal) => IDockviewDisposable
): GroupPanelFrameworkComponentFactory {
return {
content: {
createComponent: (
_id: string,
componentId: string,
component: React.FunctionComponent<IDockviewPanelProps>
): IContentRenderer => {
return new ReactPanelContentPart(componentId, component, {
addPortal,
});
},
},
tab: {
createComponent: (
_id: string,
componentId: string,
component: React.FunctionComponent<IDockviewPanelHeaderProps>
): ITabRenderer => {
return new ReactPanelHeaderPart(componentId, component, {
addPortal,
});
},
},
watermark: {
createComponent: (
_id: string,
componentId: string,
component: React.FunctionComponent<{}>
) => {
return new ReactWatermarkPart(componentId, component, {
addPortal,
});
},
},
};
}
export const DockviewReact = React.forwardRef(
(props: IDockviewReactProps, ref: React.ForwardedRef<HTMLDivElement>) => {
@ -97,57 +127,37 @@ export const DockviewReact = React.forwardRef(
React.useImperativeHandle(ref, () => domRef.current!, []);
const prevProps = React.useRef<Partial<IDockviewReactProps>>({});
React.useEffect(
() => {
const changes: Partial<DockviewOptions> = {};
PROPERTY_KEYS.forEach((propKey) => {
const key = propKey as keyof DockviewOptions;
const propValue = props[key];
if (propValue !== prevProps.current[key]) {
changes[key] = propValue as any;
}
});
if (dockviewRef.current) {
dockviewRef.current.updateOptions(changes);
} else {
// not yet fully initialized
}
prevProps.current = props;
},
PROPERTY_KEYS.map((key) => props[key])
);
React.useEffect(() => {
if (!domRef.current) {
return () => {
// noop
};
return;
}
const factory: GroupPanelFrameworkComponentFactory = {
content: {
createComponent: (
_id: string,
componentId: string,
component: React.FunctionComponent<IDockviewPanelProps>
): IContentRenderer => {
return new ReactPanelContentPart(
componentId,
component,
{
addPortal,
}
);
},
},
tab: {
createComponent: (
_id: string,
componentId: string,
component: React.FunctionComponent<IDockviewPanelHeaderProps>
): ITabRenderer => {
return new ReactPanelHeaderPart(
componentId,
component,
{
addPortal,
}
);
},
},
watermark: {
createComponent: (
_id: string,
componentId: string,
component: React.FunctionComponent<{}>
) => {
return new ReactWatermarkPart(componentId, component, {
addPortal,
});
},
},
};
const frameworkTabComponents = props.tabComponents ?? {};
if (props.defaultTabComponent) {
@ -155,40 +165,32 @@ export const DockviewReact = React.forwardRef(
props.defaultTabComponent;
}
const dockview = new DockviewComponent({
parentElement: domRef.current,
frameworkComponentFactory: factory,
frameworkComponents: props.components,
disableAutoResizing: props.disableAutoResizing,
frameworkTabComponents,
watermarkFrameworkComponent: props.watermarkComponent,
defaultTabComponent: props.defaultTabComponent
? DEFAULT_REACT_TAB
: undefined,
styles: props.hideBorders
? { separatorBorder: 'transparent' }
: undefined,
showDndOverlay: props.showDndOverlay,
createLeftHeaderActionsElement: createGroupControlElement(
const frameworkOptions: DockviewFrameworkOptions = {
headerLeftActionComponent: createGroupControlElement(
props.leftHeaderActionsComponent,
{ addPortal }
),
createRightHeaderActionsElement: createGroupControlElement(
headerRightActionComponent: createGroupControlElement(
props.rightHeaderActionsComponent,
{ addPortal }
),
createPrefixHeaderActionsElement: createGroupControlElement(
headerPrefixActionComponent: createGroupControlElement(
props.prefixHeaderActionsComponent,
{ addPortal }
),
singleTabMode: props.singleTabMode,
disableFloatingGroups: props.disableFloatingGroups,
floatingGroupBounds: props.floatingGroupBounds,
defaultRenderer: props.defaultRenderer,
debug: props.debug,
rootOverlayModel: props.rootOverlayModel,
locked: props.locked,
disableDnd: props.disableDnd,
frameworkTabComponents,
frameworkComponents: props.components,
frameworkComponentFactory: createFrameworkFactory(addPortal),
parentElement: domRef.current,
defaultTabComponent: props.defaultTabComponent
? DEFAULT_REACT_TAB
: undefined,
watermarkFrameworkComponent: props.watermarkComponent,
};
const dockview = new DockviewComponent({
...extractCoreOptions(props),
...frameworkOptions,
});
const { clientWidth, clientHeight } = domRef.current;
@ -205,24 +207,6 @@ export const DockviewReact = React.forwardRef(
};
}, []);
React.useEffect(() => {
if (!dockviewRef.current) {
return;
}
dockviewRef.current.locked = !!props.locked;
}, [props.locked]);
React.useEffect(() => {
if (!dockviewRef.current) {
return;
}
dockviewRef.current.updateOptions({
disableDnd: props.disableDnd,
});
}, [props.disableDnd]);
React.useEffect(() => {
if (!dockviewRef.current) {
return () => {
@ -241,6 +225,26 @@ export const DockviewReact = React.forwardRef(
};
}, [props.onDidDrop]);
React.useEffect(() => {
if (!dockviewRef.current) {
return () => {
// noop
};
}
const disposable = dockviewRef.current.onUnhandledDragOverEvent(
(event) => {
if (props.showDndOverlay?.(event)) {
event.accept();
}
}
);
return () => {
disposable.dispose();
};
}, [props.showDndOverlay]);
React.useEffect(() => {
if (!dockviewRef.current) {
return () => {
@ -268,15 +272,6 @@ export const DockviewReact = React.forwardRef(
});
}, [props.components]);
React.useEffect(() => {
if (!dockviewRef.current) {
return;
}
dockviewRef.current.updateOptions({
floatingGroupBounds: props.floatingGroupBounds,
});
}, [props.floatingGroupBounds]);
React.useEffect(() => {
if (!dockviewRef.current) {
return;
@ -286,33 +281,6 @@ export const DockviewReact = React.forwardRef(
});
}, [props.watermarkComponent]);
React.useEffect(() => {
if (!dockviewRef.current) {
return;
}
dockviewRef.current.updateOptions({
showDndOverlay: props.showDndOverlay,
});
}, [props.showDndOverlay]);
React.useEffect(() => {
if (!dockviewRef.current) {
return;
}
dockviewRef.current.updateOptions({
frameworkTabComponents: props.tabComponents,
});
}, [props.tabComponents]);
React.useEffect(() => {
if (!dockviewRef.current) {
return;
}
dockviewRef.current.updateOptions({
disableFloatingGroups: props.disableFloatingGroups,
});
}, [props.disableFloatingGroups]);
React.useEffect(() => {
if (!dockviewRef.current) {
return;
@ -331,14 +299,14 @@ export const DockviewReact = React.forwardRef(
: undefined,
frameworkTabComponents,
});
}, [props.defaultTabComponent]);
}, [props.tabComponents, props.defaultTabComponent]);
React.useEffect(() => {
if (!dockviewRef.current) {
return;
}
dockviewRef.current.updateOptions({
createRightHeaderActionsElement: createGroupControlElement(
headerRightActionComponent: createGroupControlElement(
props.rightHeaderActionsComponent,
{ addPortal }
),
@ -350,7 +318,7 @@ export const DockviewReact = React.forwardRef(
return;
}
dockviewRef.current.updateOptions({
createLeftHeaderActionsElement: createGroupControlElement(
headerLeftActionComponent: createGroupControlElement(
props.leftHeaderActionsComponent,
{ addPortal }
),
@ -362,16 +330,7 @@ export const DockviewReact = React.forwardRef(
return;
}
dockviewRef.current.updateOptions({
rootOverlayModel: props.rootOverlayModel,
});
}, [props.rootOverlayModel]);
React.useEffect(() => {
if (!dockviewRef.current) {
return;
}
dockviewRef.current.updateOptions({
createPrefixHeaderActionsElement: createGroupControlElement(
headerRightActionComponent: createGroupControlElement(
props.prefixHeaderActionsComponent,
{ addPortal }
),

View File

@ -1,25 +1,17 @@
import React from 'react';
import { ReactPart, ReactPortalStore } from '../react';
import {
IDockviewPanel,
DockviewCompositeDisposable,
DockviewMutableDisposable,
DockviewApi,
DockviewGroupPanel,
DockviewGroupPanelApi,
PanelUpdateEvent,
IHeaderActionsRenderer,
IDockviewHeaderActionsProps,
} from 'dockview-core';
export interface IDockviewHeaderActionsProps {
api: DockviewGroupPanelApi;
containerApi: DockviewApi;
panels: IDockviewPanel[];
activePanel: IDockviewPanel | undefined;
isGroupActive: boolean;
group: DockviewGroupPanel;
}
export class ReactHeaderActionsRendererPart {
export class ReactHeaderActionsRendererPart implements IHeaderActionsRenderer {
private mutableDisposable = new DockviewMutableDisposable();
private _element: HTMLElement;
private _part?: ReactPart<IDockviewHeaderActionsProps>;
@ -32,10 +24,6 @@ export class ReactHeaderActionsRendererPart {
return this._part;
}
get group(): DockviewGroupPanel {
return this._group;
}
constructor(
private readonly component: React.FunctionComponent<IDockviewHeaderActionsProps>,
private readonly reactPortalStore: ReactPortalStore,
@ -47,11 +35,7 @@ export class ReactHeaderActionsRendererPart {
this._element.style.width = '100%';
}
focus(): void {
// TODO
}
public init(parameters: {
init(parameters: {
containerApi: DockviewApi;
api: DockviewGroupPanelApi;
}): void {
@ -85,15 +69,15 @@ export class ReactHeaderActionsRendererPart {
);
}
public update(event: PanelUpdateEvent): void {
this._part?.update(event.params);
}
public dispose(): void {
dispose(): void {
this.mutableDisposable.dispose();
this._part?.dispose();
}
update(event: PanelUpdateEvent): void {
this._part?.update(event.params);
}
private updatePanels(): void {
this.update({ params: { panels: this._group.model.panels } });
}

View File

@ -1,12 +1,12 @@
import React from 'react';
import { ReactPart, ReactPortalStore } from '../react';
import { IDockviewPanelProps } from '../dockview/dockview';
import {
DockviewEmitter,
DockviewEvent,
PanelUpdateEvent,
IContentRenderer,
GroupPanelContentPartInitParameters,
GroupPanelPartInitParameters,
IDockviewPanelProps,
} from 'dockview-core';
export class ReactPanelContentPart implements IContentRenderer {
@ -38,7 +38,7 @@ export class ReactPanelContentPart implements IContentRenderer {
// TODO
}
public init(parameters: GroupPanelContentPartInitParameters): void {
public init(parameters: GroupPanelPartInitParameters): void {
this.part = new ReactPart(
this.element,
this.reactPortalStore,

View File

@ -1,10 +1,10 @@
import React from 'react';
import { ReactPart, ReactPortalStore } from '../react';
import { IGroupPanelBaseProps } from './dockview';
import {
PanelUpdateEvent,
ITabRenderer,
GroupPanelPartInitParameters,
IGroupPanelBaseProps,
} from 'dockview-core';
export class ReactPanelHeaderPart implements ITabRenderer {

View File

@ -6,16 +6,9 @@ import {
GroupPanelPartInitParameters,
IWatermarkRenderer,
WatermarkRendererInitParameters,
DockviewApi,
IDockviewGroupPanel,
IWatermarkPanelProps,
} from 'dockview-core';
export interface IWatermarkPanelProps {
containerApi: DockviewApi;
group?: IDockviewGroupPanel;
close: () => void;
}
export class ReactWatermarkPart implements IWatermarkRenderer {
private _element: HTMLElement;
private part?: ReactPart<IWatermarkPanelProps>;
@ -44,11 +37,6 @@ export class ReactWatermarkPart implements IWatermarkRenderer {
{
group: parameters.group,
containerApi: parameters.containerApi,
close: () => {
if (parameters.group) {
parameters.containerApi.removeGroup(parameters.group);
}
},
}
);
}

View File

@ -8,8 +8,7 @@ import {
} from 'dockview-core';
import { ReactGridPanelView } from './view';
import { usePortalsLifecycle } from '../react';
import { PanelCollection, PanelParameters } from '../types';
import { PanelParameters } from '../types';
export interface GridviewReadyEvent {
api: GridviewApi;
}
@ -23,7 +22,7 @@ export interface IGridviewPanelProps<T extends { [index: string]: any } = any>
export interface IGridviewReactProps {
orientation?: Orientation;
onReady: (event: GridviewReadyEvent) => void;
components: PanelCollection<IGridviewPanelProps>;
components: Record<string, React.FunctionComponent<IGridviewPanelProps>>;
hideBorders?: boolean;
className?: string;
proportionalLayout?: boolean;

View File

@ -4,8 +4,6 @@ export * from './dockview/dockview';
export * from './dockview/defaultTab';
export * from './splitview/splitview';
export * from './gridview/gridview';
export { IDockviewHeaderActionsProps } from './dockview/headerActionsRenderer';
export { IWatermarkPanelProps } from './dockview/reactWatermarkPart';
export * from './paneview/paneview';
export * from './types';
export * from './react';

View File

@ -9,7 +9,7 @@ import {
} from 'dockview-core';
import { usePortalsLifecycle } from '../react';
import { PanePanelSection } from './view';
import { PanelCollection, PanelParameters } from '../types';
import { PanelParameters } from '../types';
export interface PaneviewReadyEvent {
api: PaneviewApi;
@ -24,8 +24,11 @@ export interface IPaneviewPanelProps<T extends { [index: string]: any } = any>
export interface IPaneviewReactProps {
onReady: (event: PaneviewReadyEvent) => void;
components: PanelCollection<IPaneviewPanelProps>;
headerComponents?: PanelCollection<IPaneviewPanelProps>;
components: Record<string, React.FunctionComponent<IPaneviewPanelProps>>;
headerComponents?: Record<
string,
React.FunctionComponent<IPaneviewPanelProps>
>;
className?: string;
disableAutoResizing?: boolean;
disableDnd?: boolean;

View File

@ -7,7 +7,7 @@ import {
Orientation,
} from 'dockview-core';
import { usePortalsLifecycle } from '../react';
import { PanelCollection, PanelParameters } from '../types';
import { PanelParameters } from '../types';
import { ReactPanelView } from './view';
export interface SplitviewReadyEvent {
@ -23,7 +23,7 @@ export interface ISplitviewPanelProps<T extends { [index: string]: any } = any>
export interface ISplitviewReactProps {
orientation?: Orientation;
onReady: (event: SplitviewReadyEvent) => void;
components: PanelCollection<ISplitviewPanelProps>;
components: Record<string, React.FunctionComponent<ISplitviewPanelProps>>;
proportionalLayout?: boolean;
hideBorders?: boolean;
className?: string;

View File

@ -1,10 +1,5 @@
import * as React from 'react';
import { Parameters } from 'dockview-core';
export interface PanelCollection<T extends object> {
[name: string]: React.FunctionComponent<T>;
}
export interface PanelParameters<T extends {} = Parameters> {
params: T;
}

View File

@ -18,3 +18,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/static/templates

View File

@ -0,0 +1,41 @@
---
slug: dockview-1.13.0-release
title: Dockview 1.13.0
tags: [release]
---
# Release Notes
Please reference docs @ [dockview.dev](https://dockview.dev).
## 🚀 Features
- Add `onDidActivePanelChange` event to group api [#541](https://github.com/mathuo/dockview/pull/541)
## 🛠 Miscs
- Create framework packages in preperation for multiple framework support [#541](https://github.com/mathuo/dockview/pull/541)
These are still in active development and will be offically support soon.
- Create `dockview-react` package
- Create `dockview-angular` package
- Create `dockview-vue` package
- Move various type definitions from `dockview` to `dockview-core` in preperation for multiple framework support [#541](https://github.com/mathuo/dockview/pull/541)
- Move `IGroupPanelBaseProps` from `dockview` to `dockview-core`
- Move `IDockviewPanelHeaderProps` from `dockview` to `dockview-core`
- Move `IDockviewPanelProps` from `dockview` to `dockview-core`
- Move `IDockviewHeaderActionsProps ` from `dockview` to `dockview-core`
- Move `IGroupHeaderProps` from `dockview` to `dockview-core`
- Move `IWatermarkPanelProps` from `dockview` to `dockview-core`
- Move `DockviewReadyEvent` from `dockview` to `dockview-core`
- [dockview] Depreciate `canDisplayOverlay` in favour of the `onUnhandledDragOverEvent` api event [#541](https://github.com/mathuo/dockview/pull/541)
## 🔥 Breaking changes
- [dockview-core] Replace DockviewComponent `canDisplayOverlay` option with `onUnhandledDragOverEvent` event [#541](https://github.com/mathuo/dockview/pull/541)
- [dockview-core] Rename `createRightHeaderActionsElement` to `headerRightActionComponent` [#541](https://github.com/mathuo/dockview/pull/541)
- [dockview-core] Rename `createLeftHeaderActionsElement` to `headerLeftActionComponent` [#541](https://github.com/mathuo/dockview/pull/541)
- [dockview-core] Rename `createPrefixHeaderActionsElement` to `headerPrefixActionComponent` [#541](https://github.com/mathuo/dockview/pull/541)

View File

@ -3,12 +3,11 @@ title: Nested Instances
---
import { MultiFrameworkContainer } from '@site/src/components/ui/container';
import NestedDockview from '@site/sandboxes/nested-dockview/src/app';
import { CodeRunner } from '@site/src/components/ui/codeRunner';
# Nested Dockviews
You can safely create multiple dockview instances within one page and nest dockviews within other dockviews.
If you wish to interact with the drop event from one dockview instance in another dockview instance you can implement the `showDndOverlay` and `onDidDrop` props on `DockviewReact`.
<MultiFrameworkContainer sandboxId="nested-dockview" react={NestedDockview} />
<CodeRunner id="dockview/nested" />

View File

@ -2,7 +2,6 @@
title: Constraints
---
import LiveExample from '@site/src/components/ui/exampleFrame';
import { DocRef } from '@site/src/components/ui/reference/docRef'
:::warning
@ -13,5 +12,5 @@ Constraints come with several caveats. They are not serialized with layouts and
## Live Example
<LiveExample framework="react" id="dockview/constraints"/>
<CodeRunner id="dockview/constraints"/>

View File

@ -2,7 +2,6 @@
title: Group Controls
---
import LiveExample from '@site/src/components/ui/exampleFrame';
import { DocRef } from '@site/src/components/ui/reference/docRef';
This section describes how you can customize the header component of each group.
@ -39,5 +38,5 @@ return <DockviewReact
## Live Example
<LiveExample framework="react" id="dockview/group-actions"/>
<CodeRunner id="dockview/group-actions"/>

View File

@ -3,7 +3,6 @@ title: Floating Groups
---
import useBaseUrl from '@docusaurus/useBaseUrl';
import LiveExample from '@site/src/components/ui/exampleFrame';
import { DocRef } from '@site/src/components/ui/reference/docRef';
This section describes floating groups.
@ -76,4 +75,4 @@ You can check whether a group is floating via the `group.api.location` property.
## Live Example
<LiveExample framework="react" id="dockview/floating-groups"/>
<CodeRunner id="dockview/floating-groups"/>

View File

@ -3,7 +3,6 @@ title: Hidden Header
---
import useBaseUrl from '@docusaurus/useBaseUrl';
import LiveExample from '@site/src/components/ui/exampleFrame';
import { DocRef } from '@site/src/components/ui/reference/docRef';
You may wish to hide the header section of a group. This can achieved through the `hidden` variable on `panel.group.header`.

View File

@ -2,8 +2,7 @@
title: Locked Groups
---
import { MultiFrameworkContainer } from '@site/src/components/ui/container';
import DockviewLockedGroup from '@site/sandboxes/lockedgroup-dockview/src/app';
import { CodeRunner } from '@site/src/components/ui/codeRunner';
## Locked group
@ -21,7 +20,4 @@ panel.group.locked = 'no-drop-target';
Use `true` to keep drop zones top, right, bottom, left for the group. Use `no-drop-target` to disable all drop zones. For you to get a
better understanding of what this means, try and drag the panels in the example below to the locked groups.
<MultiFrameworkContainer
sandboxId="lockedgroup-dockview"
react={DockviewLockedGroup}
/>
<CodeRunner id="dockview/locked"/>

View File

@ -3,7 +3,6 @@ title: Maximized Groups
---
import { DocRef } from '@site/src/components/ui/reference/docRef';
import LiveExample from '@site/src/components/ui/exampleFrame';
This section described how to maxmimize groups.
@ -51,7 +50,7 @@ The methods exist on the panel `api` object for convenience.
## Live Examples
<LiveExample framework="react" id="dockview/maximize-group"/>
<CodeRunner id="dockview/maximize-group"/>

View File

@ -2,7 +2,6 @@
title: Popout Windows
---
import LiveExample from '@site/src/components/ui/exampleFrame';
import { DocRef } from '@site/src/components/ui/reference/docRef';
This section describes have to create popout windows.
@ -57,4 +56,4 @@ in it's original location within the grid. If the dock cannot determine the orig
choose a new location.
<LiveExample framework="react" id="dockview/popout-group"/>
<CodeRunner id="dockview/popout-group"/>

View File

@ -2,7 +2,6 @@
title: Resizing
---
import LiveExample from '@site/src/components/ui/exampleFrame';
import { DocRef } from '@site/src/components/ui/reference/docRef';
<DocRef declaration="DockviewGroupPanelApi" methods={['height', 'width', 'setSize', 'onDidDimensionsChange']} />
@ -34,4 +33,4 @@ props.api.group.api.setSize({
You can see an example invoking both approaches below.
<LiveExample framework="react" id="dockview/resize"/>
<CodeRunner framework="react" id="dockview/resize"/>

View File

@ -5,7 +5,6 @@ title: Locked
import useBaseUrl from '@docusaurus/useBaseUrl';
import { DocRef } from '@site/src/components/ui/reference/docRef';
import LiveExample from '@site/src/components/ui/exampleFrame';
This section describes how to lock the dock to prevent movement.
@ -13,4 +12,4 @@ This section describes how to lock the dock to prevent movement.
You may want to combine this with `disableDnd={true}` to provide a locked grid with no dnd funtionality. See [Disable Dnd](/docs/core/dnd/disable) for more.
:::
<LiveExample framework='react' id='dockview/locked'/>
<CodeRunner id='dockview/locked'/>

View File

@ -3,8 +3,6 @@ title: Overview
sidebar_position: 0
---
import LiveExample from '@site/src/components/ui/exampleFrame';
This section provided a core overview.
The component takes a collection of [Options](/docs/api/dockview/options) as inputs and
@ -33,10 +31,3 @@ const component = new DockviewComponent({
```
</FrameworkSpecific>
## Container Resizing
The component will automatically resize to it's container.
<LiveExample framework="react" id="dockview/resize-container"/>
# Disposal Pattern

View File

@ -4,7 +4,7 @@ sidebar_position: 1
---
import { DocRef } from '@site/src/components/ui/reference/docRef';
import LiveExample from '@site/src/components/ui/exampleFrame';
import { CodeRunner } from '@site/src/components/ui/codeRunner';
This section describes how to add a new panel and the options you can provide.
@ -48,7 +48,7 @@ api.addPanel({
api.setTitle('my_new_custom_title');
```
<LiveExample framework="react" id="dockview/update-title" height={250}/>
<CodeRunner id="dockview/update-title" height={250}/>
## Provide a custom Tab renderer

View File

@ -4,11 +4,9 @@ sidebar_postiion: 5
---
import { MultiFrameworkContainer } from '@site/src/components/ui/container';
import { CodeRunner } from '@site/src/components/ui/codeRunner';
import RenderingDockview from '@site/sandboxes/rendering-dockview/src/app';
import LiveExample from '@site/src/components/ui/exampleFrame';
Rendering type is an important consideration when creating your application and whether your panels should be destroyed when hidden.
:::info
@ -70,7 +68,7 @@ api.addPanel({
## Live Example
<LiveExample framework="react" id="dockview/render-mode"/>
<CodeRunner id="dockview/render-mode"/>
By default `DockviewReact` only adds to the DOM those panels that are visible,
@ -130,3 +128,4 @@ Toggling the checkbox you can see that when you only render those panels which a
sandboxId="rendering-dockview"
react={RenderingDockview}
/>

View File

@ -4,7 +4,7 @@ title: Resizing
This section describes how to programatically resize a panel.
import LiveExample from '@site/src/components/ui/exampleFrame';
import { CodeRunner } from '@site/src/components/ui/codeRunner';
import { DocRef } from '@site/src/components/ui/reference/docRef';
<DocRef declaration="DockviewPanelApi" methods={['height', 'width', 'setSize', 'onDidDimensionsChange']} />
@ -37,4 +37,4 @@ props.api.group.api.setSize({
You can see an example invoking both approaches below.
<LiveExample framework="react" id="dockview/resize"/>
<CodeRunner id="dockview/resize"/>

View File

@ -4,7 +4,7 @@ sidebar_position: 2
---
import { MultiFrameworkContainer } from '@site/src/components/ui/container';
import CustomHeadersDockview from '@site/sandboxes/customheader-dockview/src/app';
import { CodeRunner } from '@site/src/components/ui/codeRunner';
import DockviewNative from '@site/sandboxes/fullwidthtab-dockview/src/app';
import { attach as attachNativeDockview } from '@site/sandboxes/javascript/fullwidthtab-dockview/src/app';
import { DocRef } from '@site/src/components/ui/reference/docRef';
@ -99,10 +99,7 @@ As a simple example the below attaches a custom event handler for the context me
The below example uses a custom tab renderer to reigster a popover when the user right clicked on a tab.
This still makes use of the `DockviewDefaultTab` since it's only a minor change.
<MultiFrameworkContainer
sandboxId="customheader-dockview"
react={CustomHeadersDockview}
/>
<CodeRunner id="dockview/custom-header"/>
## Full Width Tab
@ -126,15 +123,7 @@ return <DockviewReactComponent singleTabMode="fullwidth" />
typescript={attachNativeDockview}
/>
import DockviewTabheight from '@site/sandboxes/tabheight-dockview/src/app';
import { attach as attachTabHeightDockview } from '@site/sandboxes/javascript/tabheight-dockview/src/app';
## Tab Height
Tab height can be controlled through CSS.
<MultiFrameworkContainer
sandboxId="tabheight-dockview"
react={DockviewTabheight}
typescript={attachTabHeightDockview}
/>

View File

@ -43,4 +43,4 @@ panel.api.updateParameters({
## Live Example
<LiveExample framework="react" id="dockview/update-parameters"/>
<CodeRunner id="dockview/update-parameters"/>

View File

@ -2,8 +2,6 @@
title: Scrolling
---
import LiveExample from '@site/src/components/ui/exampleFrame';
It's important to understand how to configure the scrollbar within a panel.
A panel will appear with a scrollbar if the the contents of your view has a fixed height.
@ -17,4 +15,4 @@ The following example contains three views:
- **Panel 2** (`height: 2000px`): A scrollbar does appear since a fixed height has been used.
- **Panel 3**: `height: 100%` and a child component with `overflow: auto` which will enable scrollbars.
<LiveExample framework="react" id="dockview/scrollbars"/>
<CodeRunner id="dockview/scrollbars"/>

View File

@ -2,8 +2,7 @@
title: Loading State
---
import { MultiFrameworkContainer } from '@site/src/components/ui/container';
import LiveExample from '@site/src/components/ui/exampleFrame';
import { CodeRunner } from '@site/src/components/ui/codeRunner';
import { DocRef } from '@site/src/components/ui/reference/docRef';
This section described loading a dock layout.
@ -43,5 +42,6 @@ return <DockviewComponent onReady={onReady}/>;
# Live Example
<LiveExample framework="react" id="dockview/layout"/>
<CodeRunner id="dockview/layout"/>

View File

@ -2,9 +2,7 @@
title: Saving State
---
import { MultiFrameworkContainer } from '@site/src/components/ui/container';
import LiveExample from '@site/src/components/ui/exampleFrame';
import { CodeRunner } from '@site/src/components/ui/codeRunner';
import { DocRef } from '@site/src/components/ui/reference/docRef';
This section describes how to serialize a dockview instance.
@ -39,5 +37,4 @@ return <DockviewComponent onReady={onReady}/>
# Live Example
<LiveExample framework="react" id="dockview/layout"/>
<CodeRunner id="dockview/layout"/>

View File

@ -5,7 +5,6 @@ title: Watermark
import useBaseUrl from '@docusaurus/useBaseUrl';
import { DocRef } from '@site/src/components/ui/reference/docRef';
import LiveExample from '@site/src/components/ui/exampleFrame';
When there is nothing else to display.
@ -28,4 +27,4 @@ The following properties can be set to configure the behaviours of floating grou
## Live Examples
<LiveExample framework="react" id="dockview/watermark"/>
<CodeRunner id="dockview/watermark"/>

View File

@ -5,6 +5,4 @@ sidebar_position: 3
A *tabview* can be created using a dock and preventing some default behaviours.
import LiveExample from '@site/src/components/ui/exampleFrame';
<LiveExample framework='react' id='dockview/tabview' />
<CodeRunner id='dockview/tabview' />

View File

@ -7,7 +7,7 @@ sidebar_position: 0
Learn how to install Dockview for a selection of frameworks.
<FrameworkSpecific framework='JavaScript'>
Firstly, install the `dockvire-core` library:
Firstly, install the `dockview-core` library:
```sh
npm install dockview-core
@ -21,3 +21,4 @@ Firstly, install the `dockview` library:
npm install dockview
```
</FrameworkSpecific>

View File

@ -5,9 +5,7 @@ description: Gridview Documentation
import { MultiFrameworkContainer } from '@site/src/components/ui/container';
import SimpleGridview from '@site/sandboxes/simple-gridview/src/app';
import EditorGridview from '@site/sandboxes/editor-gridview/src/app';
// import SimpleGridview from '@site/sandboxes/simple-gridview/src/app';
import { EventsGridview } from '@site/src/components/gridview/events';
// import IDEExample from '@site/sandboxes/ide-example/src/app';
import Link from '@docusaurus/Link';
import { DocRef } from '@site/src/components/ui/reference/docRef';

View File

@ -4,10 +4,9 @@ sidebar_position: 0
---
import { MultiFrameworkContainer } from '@site/src/components/ui/container';
import { CodeRunner } from '@site/src/components/ui/codeRunner';
import SimpleDockview from '@site/sandboxes/simple-dockview/src/app';
import DockviewExampleApp from '@site/sandboxes/example-app-dockview/src/app';
import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simple-dockview/src/app';
<MultiFrameworkContainer
@ -20,11 +19,7 @@ import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simpl
Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels.
<MultiFrameworkContainer
sandboxId="simple-dockview"
react={SimpleDockview}
typescript={attachSimpleDockview}
/>
<CodeRunner id="dockview/basic"/>
<br />
@ -60,19 +55,3 @@ const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => {
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
};
```

Some files were not shown because too many files have changed in this diff Show More