mirror of
https://github.com/mathuo/dockview
synced 2025-11-08 16:10:10 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
101ccefc18 | ||
|
|
84ddfdcedc | ||
|
|
e581f93ced | ||
|
|
c6ee934974 | ||
|
|
5d35ba2c23 | ||
|
|
a37d464a8a | ||
|
|
7397dde9b9 | ||
|
|
9e6eaf5c77 | ||
|
|
897b96b7df | ||
|
|
9908429e38 | ||
|
|
c0d17de4b4 | ||
|
|
53e869e03c | ||
|
|
fe70a06be0 | ||
|
|
be32ba731d | ||
|
|
a7bef6db80 | ||
|
|
58c8a411ab | ||
|
|
5f53d6fcd7 | ||
|
|
9328b78cc3 | ||
|
|
44172b7937 | ||
|
|
eec2ae04f3 |
@ -2,7 +2,7 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "4.8.0",
|
||||
"version": "4.11.0",
|
||||
"npmClient": "yarn",
|
||||
"command": {
|
||||
"publish": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-angular",
|
||||
"version": "4.8.0",
|
||||
"version": "4.11.0",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
@ -53,7 +53,7 @@
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-angular --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"dockview-core": "^4.7.1"
|
||||
"dockview-core": "^4.11.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": ">=14.0.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-core",
|
||||
"version": "4.7.1",
|
||||
"version": "4.11.0",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
|
||||
@ -132,12 +132,12 @@ describe('dockviewGroupPanel', () => {
|
||||
|
||||
cut.model.openPanel(panel);
|
||||
|
||||
// active panel constraints
|
||||
// explicit group constraints now override panel constraints
|
||||
|
||||
expect(cut.minimumWidth).toBe(21);
|
||||
expect(cut.minimumHeight).toBe(11);
|
||||
expect(cut.maximumHeight).toBe(101);
|
||||
expect(cut.maximumWidth).toBe(201);
|
||||
expect(cut.minimumWidth).toBe(20); // group constraint overrides panel constraint
|
||||
expect(cut.minimumHeight).toBe(10); // group constraint overrides panel constraint
|
||||
expect(cut.maximumHeight).toBe(100); // group constraint overrides panel constraint
|
||||
expect(cut.maximumWidth).toBe(200); // group constraint overrides panel constraint
|
||||
|
||||
const panel2 = new DockviewPanel(
|
||||
'panel_id',
|
||||
@ -158,12 +158,12 @@ describe('dockviewGroupPanel', () => {
|
||||
|
||||
cut.model.openPanel(panel2);
|
||||
|
||||
// active panel constraints
|
||||
// explicit group constraints still override panel constraints
|
||||
|
||||
expect(cut.minimumWidth).toBe(22);
|
||||
expect(cut.minimumHeight).toBe(12);
|
||||
expect(cut.maximumHeight).toBe(102);
|
||||
expect(cut.maximumWidth).toBe(202);
|
||||
expect(cut.minimumWidth).toBe(20); // group constraint overrides panel constraint
|
||||
expect(cut.minimumHeight).toBe(10); // group constraint overrides panel constraint
|
||||
expect(cut.maximumHeight).toBe(100); // group constraint overrides panel constraint
|
||||
expect(cut.maximumWidth).toBe(200); // group constraint overrides panel constraint
|
||||
|
||||
const panel3 = new DockviewPanel(
|
||||
'panel_id',
|
||||
|
||||
@ -2936,4 +2936,69 @@ describe('gridview', () => {
|
||||
expect(panel1.api.isVisible).toBeTruthy();
|
||||
expect(panel2.api.isVisible).toBeTruthy();
|
||||
});
|
||||
|
||||
test('registerPanel is called after doAddGroup - panel api events work immediately', () => {
|
||||
// This test verifies the fix for the timing issue where registerPanel
|
||||
// was called before doAddGroup, causing "Cannot read properties of undefined" errors
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
|
||||
// Add first panel
|
||||
const panel1 = gridview.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
// Verify the panel API is immediately accessible and functional
|
||||
expect(panel1.api).toBeDefined();
|
||||
expect(panel1.api.onDidFocusChange).toBeDefined();
|
||||
|
||||
// Subscribe to focus events to verify event subscription works
|
||||
let focusEventCount = 0;
|
||||
const disposable = panel1.api.onDidFocusChange((event) => {
|
||||
focusEventCount++;
|
||||
});
|
||||
|
||||
// This should not throw an error - before the fix, this would throw:
|
||||
// "Cannot read properties of undefined (reading 'onDidFocusChange')"
|
||||
const panel2 = gridview.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
position: { referencePanel: panel1.id, direction: 'right' },
|
||||
});
|
||||
|
||||
// Verify both panels have working APIs
|
||||
expect(panel1.api).toBeDefined();
|
||||
expect(panel2.api).toBeDefined();
|
||||
expect(panel1.api.onDidFocusChange).toBeDefined();
|
||||
expect(panel2.api.onDidFocusChange).toBeDefined();
|
||||
|
||||
// Verify that the API is functional by checking properties
|
||||
expect(panel1.api.isVisible).toBeTruthy();
|
||||
expect(panel2.api.isVisible).toBeTruthy();
|
||||
|
||||
// Verify we can subscribe to events on the second panel too
|
||||
const disposable2 = panel2.api.onDidFocusChange((event) => {
|
||||
focusEventCount++;
|
||||
});
|
||||
|
||||
// Clean up
|
||||
disposable.dispose();
|
||||
disposable2.dispose();
|
||||
|
||||
// The main test is that we got this far without errors
|
||||
expect(true).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@ -33,7 +33,7 @@ export interface DockviewPanelApi
|
||||
extends Omit<
|
||||
GridviewPanelApi,
|
||||
// omit properties that do not make sense here
|
||||
'setVisible' | 'onDidConstraintsChange' | 'setConstraints'
|
||||
'setVisible' | 'onDidConstraintsChange'
|
||||
> {
|
||||
/**
|
||||
* The id of the tab component renderer
|
||||
|
||||
@ -12,7 +12,7 @@ export interface IPanelDeserializer {
|
||||
): IDockviewPanel;
|
||||
}
|
||||
|
||||
// @depreciated
|
||||
// @deprecated
|
||||
interface LegacyState extends GroupviewPanelState {
|
||||
view?: {
|
||||
tab?: { id: string };
|
||||
|
||||
@ -7,12 +7,13 @@ import {
|
||||
IHeader,
|
||||
DockviewGroupPanelLocked,
|
||||
} from './dockviewGroupPanelModel';
|
||||
import { GridviewPanel, IGridviewPanel } from '../gridview/gridviewPanel';
|
||||
import { GridviewPanel, IGridviewPanel, Contraints } from '../gridview/gridviewPanel';
|
||||
import { IDockviewPanel } from '../dockview/dockviewPanel';
|
||||
import {
|
||||
DockviewGroupPanelApi,
|
||||
DockviewGroupPanelApiImpl,
|
||||
} from '../api/dockviewGroupPanelApi';
|
||||
// GridConstraintChangeEvent2 is not exported, so we'll type it manually
|
||||
|
||||
const MINIMUM_DOCKVIEW_GROUP_PANEL_WIDTH = 100;
|
||||
const MINIMUM_DOCKVIEW_GROUP_PANEL_HEIGHT = 100;
|
||||
@ -33,8 +34,16 @@ export class DockviewGroupPanel
|
||||
implements IDockviewGroupPanel
|
||||
{
|
||||
private readonly _model: DockviewGroupPanelModel;
|
||||
|
||||
// Track explicitly set constraints to override panel constraints
|
||||
private _explicitConstraints: Partial<Contraints> = {};
|
||||
|
||||
override get minimumWidth(): number {
|
||||
// Check for explicitly set group constraint first
|
||||
if (typeof this._explicitConstraints.minimumWidth === 'number') {
|
||||
return this._explicitConstraints.minimumWidth;
|
||||
}
|
||||
|
||||
const activePanelMinimumWidth = this.activePanel?.minimumWidth;
|
||||
if (typeof activePanelMinimumWidth === 'number') {
|
||||
return activePanelMinimumWidth;
|
||||
@ -43,6 +52,11 @@ export class DockviewGroupPanel
|
||||
}
|
||||
|
||||
override get minimumHeight(): number {
|
||||
// Check for explicitly set group constraint first
|
||||
if (typeof this._explicitConstraints.minimumHeight === 'number') {
|
||||
return this._explicitConstraints.minimumHeight;
|
||||
}
|
||||
|
||||
const activePanelMinimumHeight = this.activePanel?.minimumHeight;
|
||||
if (typeof activePanelMinimumHeight === 'number') {
|
||||
return activePanelMinimumHeight;
|
||||
@ -51,6 +65,11 @@ export class DockviewGroupPanel
|
||||
}
|
||||
|
||||
override get maximumWidth(): number {
|
||||
// Check for explicitly set group constraint first
|
||||
if (typeof this._explicitConstraints.maximumWidth === 'number') {
|
||||
return this._explicitConstraints.maximumWidth;
|
||||
}
|
||||
|
||||
const activePanelMaximumWidth = this.activePanel?.maximumWidth;
|
||||
if (typeof activePanelMaximumWidth === 'number') {
|
||||
return activePanelMaximumWidth;
|
||||
@ -59,6 +78,11 @@ export class DockviewGroupPanel
|
||||
}
|
||||
|
||||
override get maximumHeight(): number {
|
||||
// Check for explicitly set group constraint first
|
||||
if (typeof this._explicitConstraints.maximumHeight === 'number') {
|
||||
return this._explicitConstraints.maximumHeight;
|
||||
}
|
||||
|
||||
const activePanelMaximumHeight = this.activePanel?.maximumHeight;
|
||||
if (typeof activePanelMaximumHeight === 'number') {
|
||||
return activePanelMaximumHeight;
|
||||
@ -107,7 +131,7 @@ export class DockviewGroupPanel
|
||||
options.constraints?.minimumHeight ??
|
||||
MINIMUM_DOCKVIEW_GROUP_PANEL_HEIGHT,
|
||||
minimumWidth:
|
||||
options.constraints?.maximumHeight ??
|
||||
options.constraints?.minimumWidth ??
|
||||
MINIMUM_DOCKVIEW_GROUP_PANEL_WIDTH,
|
||||
maximumHeight: options.constraints?.maximumHeight,
|
||||
maximumWidth: options.constraints?.maximumWidth,
|
||||
@ -128,6 +152,30 @@ export class DockviewGroupPanel
|
||||
this.addDisposables(
|
||||
this.model.onDidActivePanelChange((event) => {
|
||||
this.api._onDidActivePanelChange.fire(event);
|
||||
}),
|
||||
this.api.onDidConstraintsChangeInternal((event: any) => {
|
||||
// Track explicitly set constraints to override panel constraints
|
||||
// Extract numeric values from functions or values
|
||||
if (event.minimumWidth !== undefined) {
|
||||
this._explicitConstraints.minimumWidth = typeof event.minimumWidth === 'function'
|
||||
? event.minimumWidth()
|
||||
: event.minimumWidth;
|
||||
}
|
||||
if (event.minimumHeight !== undefined) {
|
||||
this._explicitConstraints.minimumHeight = typeof event.minimumHeight === 'function'
|
||||
? event.minimumHeight()
|
||||
: event.minimumHeight;
|
||||
}
|
||||
if (event.maximumWidth !== undefined) {
|
||||
this._explicitConstraints.maximumWidth = typeof event.maximumWidth === 'function'
|
||||
? event.maximumWidth()
|
||||
: event.maximumWidth;
|
||||
}
|
||||
if (event.maximumHeight !== undefined) {
|
||||
this._explicitConstraints.maximumHeight = typeof event.maximumHeight === 'function'
|
||||
? event.maximumHeight()
|
||||
: event.maximumHeight;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@ -366,9 +366,8 @@ export class GridviewComponent
|
||||
isVisible: true,
|
||||
});
|
||||
|
||||
this.registerPanel(view);
|
||||
|
||||
this.doAddGroup(view, relativeLocation, options.size);
|
||||
this.registerPanel(view);
|
||||
this.doSetGroupActive(view);
|
||||
|
||||
return view;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-react",
|
||||
"version": "4.7.1",
|
||||
"version": "4.11.0",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
@ -53,6 +53,6 @@
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"dockview": "^4.7.1"
|
||||
"dockview": "^4.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,13 +10,13 @@ const config: JestConfigWithTsJest = {
|
||||
'<rootDir>/packages/dockview-vue/src/**/*.{js,jsx,ts,tsx}',
|
||||
],
|
||||
setupFiles: [
|
||||
// '<rootDir>/packages/dockview-vue/src/__tests__/__mocks__/resizeObserver.js',
|
||||
'<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__',
|
||||
'<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',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-vue",
|
||||
"version": "4.7.1",
|
||||
"version": "4.11.0",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
@ -53,9 +53,13 @@
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"dockview-core": "^4.7.1"
|
||||
"dockview-core": "^4.11.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/test-utils": "^2.4.0-alpha.2",
|
||||
"@vue/vue3-jest": "^29.2.6"
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
||||
observe: jest.fn(),
|
||||
unobserve: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
}));
|
||||
53
packages/dockview-vue/src/__tests__/__test_utils__/utils.ts
Normal file
53
packages/dockview-vue/src/__tests__/__test_utils__/utils.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { ComponentInternalInstance, getCurrentInstance } from 'vue';
|
||||
|
||||
export function setMockRefElement() {
|
||||
const mockElement = document.createElement('div');
|
||||
mockElement.style.width = '1000px';
|
||||
mockElement.style.height = '800px';
|
||||
|
||||
Object.defineProperty(mockElement, 'clientWidth', {
|
||||
configurable: true,
|
||||
value: 1000,
|
||||
});
|
||||
Object.defineProperty(mockElement, 'clientHeight', {
|
||||
configurable: true,
|
||||
value: 800,
|
||||
});
|
||||
Object.defineProperty(mockElement, 'offsetWidth', {
|
||||
configurable: true,
|
||||
value: 1000,
|
||||
});
|
||||
Object.defineProperty(mockElement, 'offsetHeight', {
|
||||
configurable: true,
|
||||
value: 800,
|
||||
});
|
||||
|
||||
return mockElement;
|
||||
}
|
||||
|
||||
export function createMockVueInstance(): ComponentInternalInstance {
|
||||
return {
|
||||
appContext: {
|
||||
app: {} as any,
|
||||
config: {} as any,
|
||||
mixins: [],
|
||||
components: {
|
||||
'test-component': {
|
||||
props: { params: Object, api: Object, containerApi: Object },
|
||||
template: '<div>Test Component</div>'
|
||||
}
|
||||
},
|
||||
directives: {},
|
||||
provides: {},
|
||||
globalProperties: {},
|
||||
},
|
||||
parent: null,
|
||||
components: {
|
||||
'test-component': {
|
||||
props: { params: Object, api: Object, containerApi: Object },
|
||||
template: '<div>Test Component</div>'
|
||||
}
|
||||
},
|
||||
provides: {},
|
||||
} as any;
|
||||
}
|
||||
112
packages/dockview-vue/src/__tests__/dockview.spec.ts
Normal file
112
packages/dockview-vue/src/__tests__/dockview.spec.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { createDockview, PROPERTY_KEYS_DOCKVIEW } from 'dockview-core';
|
||||
|
||||
describe('DockviewVue Component', () => {
|
||||
test('should export component types', () => {
|
||||
const types = require('../dockview/types');
|
||||
|
||||
expect(types).toBeDefined();
|
||||
expect(typeof types).toBe('object');
|
||||
});
|
||||
|
||||
test('should export dockview-core functionality', () => {
|
||||
expect(createDockview).toBeDefined();
|
||||
expect(PROPERTY_KEYS_DOCKVIEW).toBeDefined();
|
||||
expect(Array.isArray(PROPERTY_KEYS_DOCKVIEW)).toBe(true);
|
||||
});
|
||||
|
||||
test('should have correct dockview properties', () => {
|
||||
expect(PROPERTY_KEYS_DOCKVIEW).toContain('disableAutoResizing');
|
||||
expect(PROPERTY_KEYS_DOCKVIEW).toContain('hideBorders');
|
||||
expect(PROPERTY_KEYS_DOCKVIEW).toContain('theme');
|
||||
expect(PROPERTY_KEYS_DOCKVIEW).toContain('singleTabMode');
|
||||
});
|
||||
|
||||
test('should create dockview instance with DOM element', () => {
|
||||
const element = document.createElement('div');
|
||||
document.body.appendChild(element);
|
||||
|
||||
const mockRenderer = {
|
||||
element: document.createElement('div'),
|
||||
dispose: () => {},
|
||||
update: () => {},
|
||||
init: () => {}
|
||||
};
|
||||
|
||||
const api = createDockview(element, {
|
||||
disableAutoResizing: true,
|
||||
hideBorders: false,
|
||||
createComponent: () => mockRenderer
|
||||
});
|
||||
|
||||
expect(api).toBeDefined();
|
||||
expect(typeof api.layout).toBe('function');
|
||||
expect(typeof api.dispose).toBe('function');
|
||||
expect(typeof api.addPanel).toBe('function');
|
||||
expect(typeof api.updateOptions).toBe('function');
|
||||
|
||||
api.dispose();
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
test('should handle framework component creation', () => {
|
||||
const element = document.createElement('div');
|
||||
document.body.appendChild(element);
|
||||
|
||||
let createdComponent: any;
|
||||
|
||||
const api = createDockview(element, {
|
||||
createComponent: (options) => {
|
||||
createdComponent = {
|
||||
element: document.createElement('div'),
|
||||
dispose: jest.fn(),
|
||||
update: jest.fn(),
|
||||
init: jest.fn()
|
||||
};
|
||||
return createdComponent;
|
||||
}
|
||||
});
|
||||
|
||||
// Add a panel to trigger component creation
|
||||
api.addPanel({
|
||||
id: 'test-panel',
|
||||
component: 'test-component',
|
||||
title: 'Test Panel'
|
||||
});
|
||||
|
||||
expect(createdComponent).toBeDefined();
|
||||
expect(createdComponent.element).toBeInstanceOf(HTMLElement);
|
||||
|
||||
api.dispose();
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
test('should handle option updates', () => {
|
||||
const element = document.createElement('div');
|
||||
document.body.appendChild(element);
|
||||
|
||||
const mockRenderer = {
|
||||
element: document.createElement('div'),
|
||||
dispose: () => {},
|
||||
update: () => {},
|
||||
init: () => {}
|
||||
};
|
||||
|
||||
const api = createDockview(element, {
|
||||
disableAutoResizing: false,
|
||||
hideBorders: false,
|
||||
createComponent: () => mockRenderer
|
||||
});
|
||||
|
||||
// Update options
|
||||
api.updateOptions({
|
||||
disableAutoResizing: true,
|
||||
hideBorders: true
|
||||
});
|
||||
|
||||
// Test passes if no errors are thrown
|
||||
expect(true).toBe(true);
|
||||
|
||||
api.dispose();
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
});
|
||||
@ -1,5 +0,0 @@
|
||||
describe('empty', () => {
|
||||
test('that passes', () => {
|
||||
expect(true).toBeTruthy();
|
||||
});
|
||||
});
|
||||
155
packages/dockview-vue/src/__tests__/gridview.spec.ts
Normal file
155
packages/dockview-vue/src/__tests__/gridview.spec.ts
Normal file
@ -0,0 +1,155 @@
|
||||
import { createGridview, PROPERTY_KEYS_GRIDVIEW, Orientation } from 'dockview-core';
|
||||
import { VueGridviewPanelView } from '../gridview/view';
|
||||
|
||||
describe('GridviewVue Component', () => {
|
||||
test('should export component types', () => {
|
||||
const types = require('../gridview/types');
|
||||
|
||||
expect(types).toBeDefined();
|
||||
expect(typeof types).toBe('object');
|
||||
});
|
||||
|
||||
test('should export dockview-core functionality', () => {
|
||||
const dockviewCore = require('dockview-core');
|
||||
|
||||
expect(dockviewCore.createGridview).toBeDefined();
|
||||
expect(dockviewCore.PROPERTY_KEYS_GRIDVIEW).toBeDefined();
|
||||
});
|
||||
|
||||
test('should have correct gridview properties', () => {
|
||||
expect(PROPERTY_KEYS_GRIDVIEW).toContain('proportionalLayout');
|
||||
expect(PROPERTY_KEYS_GRIDVIEW).toContain('hideBorders');
|
||||
expect(PROPERTY_KEYS_GRIDVIEW).toContain('disableAutoResizing');
|
||||
});
|
||||
|
||||
test('should create gridview instance with DOM element', () => {
|
||||
const element = document.createElement('div');
|
||||
document.body.appendChild(element);
|
||||
|
||||
const api = createGridview(element, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: true,
|
||||
hideBorders: false,
|
||||
createComponent: () => new VueGridviewPanelView('test', 'test-component', { template: '<div>Test</div>' } as any, {} as any)
|
||||
});
|
||||
|
||||
expect(api).toBeDefined();
|
||||
expect(typeof api.layout).toBe('function');
|
||||
expect(typeof api.dispose).toBe('function');
|
||||
expect(typeof api.addPanel).toBe('function');
|
||||
expect(typeof api.updateOptions).toBe('function');
|
||||
|
||||
api.dispose();
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
test('should handle proportional layout changes', () => {
|
||||
const element = document.createElement('div');
|
||||
document.body.appendChild(element);
|
||||
|
||||
const api = createGridview(element, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: false,
|
||||
createComponent: () => new VueGridviewPanelView('test', 'test-component', { template: '<div>Test</div>' } as any, {} as any)
|
||||
});
|
||||
|
||||
// Update proportional layout
|
||||
api.updateOptions({ proportionalLayout: true });
|
||||
|
||||
// Test passes if no errors are thrown
|
||||
expect(true).toBe(true);
|
||||
|
||||
api.dispose();
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
test('should add and manage grid panels', () => {
|
||||
const element = document.createElement('div');
|
||||
document.body.appendChild(element);
|
||||
|
||||
const api = createGridview(element, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: true,
|
||||
createComponent: (options) => new VueGridviewPanelView(options.id, options.name, { template: '<div>Test</div>' } as any, {} as any)
|
||||
});
|
||||
|
||||
// Add a panel
|
||||
api.addPanel({
|
||||
id: 'grid-panel-1',
|
||||
component: 'test-component'
|
||||
});
|
||||
|
||||
expect(api.panels.length).toBe(1);
|
||||
expect(api.panels[0].id).toBe('grid-panel-1');
|
||||
|
||||
// Remove the panel
|
||||
api.removePanel(api.panels[0]);
|
||||
expect(api.panels.length).toBe(0);
|
||||
|
||||
api.dispose();
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
});
|
||||
|
||||
describe('VueGridviewPanelView', () => {
|
||||
let mockVueInstance: any;
|
||||
let mockVueComponent: any;
|
||||
let panelView: VueGridviewPanelView;
|
||||
|
||||
beforeEach(() => {
|
||||
mockVueInstance = {
|
||||
appContext: { components: {} },
|
||||
components: {},
|
||||
parent: null,
|
||||
};
|
||||
|
||||
mockVueComponent = {
|
||||
props: { params: Object, api: Object, containerApi: Object },
|
||||
template: '<div>Test Gridview Panel</div>',
|
||||
};
|
||||
|
||||
panelView = new VueGridviewPanelView(
|
||||
'gridview-test-id',
|
||||
'gridview-test-component',
|
||||
mockVueComponent as any,
|
||||
mockVueInstance
|
||||
);
|
||||
});
|
||||
|
||||
test('should create panel view with correct properties', () => {
|
||||
expect(panelView.id).toBe('gridview-test-id');
|
||||
expect(panelView.element).toBeInstanceOf(HTMLElement);
|
||||
expect(panelView.element.style.height).toBe('100%');
|
||||
expect(panelView.element.style.width).toBe('100%');
|
||||
expect(panelView.element.style.overflow).toBe('hidden');
|
||||
});
|
||||
|
||||
test('should implement GridviewPanel interface', () => {
|
||||
expect(panelView.api).toBeDefined();
|
||||
expect(typeof panelView.getComponent).toBe('function');
|
||||
expect(panelView.element).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
test('should create framework part when getComponent is called', () => {
|
||||
// Mock _params to avoid accessor error
|
||||
(panelView as any)._params = {
|
||||
params: {},
|
||||
accessor: { id: 'test-accessor' }
|
||||
};
|
||||
|
||||
const component = panelView.getComponent();
|
||||
expect(component).toBeDefined();
|
||||
expect(component.constructor.name).toBe('VuePart');
|
||||
});
|
||||
|
||||
test('should handle empty params', () => {
|
||||
// Mock _params to avoid accessor error
|
||||
(panelView as any)._params = {
|
||||
params: {},
|
||||
accessor: { id: 'test-accessor' }
|
||||
};
|
||||
|
||||
const component = panelView.getComponent();
|
||||
expect(component).toBeDefined();
|
||||
});
|
||||
});
|
||||
126
packages/dockview-vue/src/__tests__/paneview.spec.ts
Normal file
126
packages/dockview-vue/src/__tests__/paneview.spec.ts
Normal file
@ -0,0 +1,126 @@
|
||||
import { createPaneview, PROPERTY_KEYS_PANEVIEW } from 'dockview-core';
|
||||
import { VuePaneviewPanelView } from '../paneview/view';
|
||||
|
||||
describe('PaneviewVue Component', () => {
|
||||
test('should export component types', () => {
|
||||
const types = require('../paneview/types');
|
||||
|
||||
expect(types).toBeDefined();
|
||||
expect(typeof types).toBe('object');
|
||||
});
|
||||
|
||||
test('should export dockview-core functionality', () => {
|
||||
const dockviewCore = require('dockview-core');
|
||||
|
||||
expect(dockviewCore.createPaneview).toBeDefined();
|
||||
expect(dockviewCore.PROPERTY_KEYS_PANEVIEW).toBeDefined();
|
||||
});
|
||||
|
||||
test('should have correct paneview properties', () => {
|
||||
expect(PROPERTY_KEYS_PANEVIEW).toContain('disableAutoResizing');
|
||||
expect(PROPERTY_KEYS_PANEVIEW).toContain('disableDnd');
|
||||
});
|
||||
|
||||
test('should create paneview with Vue framework support', () => {
|
||||
// Test that Vue-specific components can be created with proper type safety
|
||||
expect(typeof createPaneview).toBe('function');
|
||||
expect(typeof VuePaneviewPanelView).toBe('function');
|
||||
|
||||
// Test that a Vue paneview panel view can be instantiated
|
||||
const mockVueComponent = { template: '<div>Test</div>' } as any;
|
||||
const mockParent = {} as any;
|
||||
|
||||
const panelView = new VuePaneviewPanelView(
|
||||
'test-id',
|
||||
mockVueComponent,
|
||||
mockParent
|
||||
);
|
||||
|
||||
expect(panelView.id).toBe('test-id');
|
||||
expect(panelView.element).toBeInstanceOf(HTMLElement);
|
||||
expect(typeof panelView.init).toBe('function');
|
||||
expect(typeof panelView.update).toBe('function');
|
||||
expect(typeof panelView.dispose).toBe('function');
|
||||
});
|
||||
|
||||
test('should handle Vue component integration for panes', () => {
|
||||
// Test Vue component factory creation for paneview
|
||||
const mockComponent = {
|
||||
template: '<div class="vue-pane-panel">{{ title }}: {{ params.content }}</div>',
|
||||
props: ['params', 'api', 'containerApi', 'title']
|
||||
};
|
||||
|
||||
expect(mockComponent.template).toContain('vue-pane-panel');
|
||||
expect(mockComponent.props).toContain('params');
|
||||
expect(mockComponent.props).toContain('api');
|
||||
expect(mockComponent.props).toContain('containerApi');
|
||||
expect(mockComponent.props).toContain('title');
|
||||
});
|
||||
});
|
||||
|
||||
describe('VuePaneviewPanelView', () => {
|
||||
test('should be a class that implements IPanePart interface', () => {
|
||||
expect(VuePaneviewPanelView).toBeDefined();
|
||||
expect(typeof VuePaneviewPanelView).toBe('function');
|
||||
});
|
||||
|
||||
test('should create instance with required properties', () => {
|
||||
const mockVueInstance = {
|
||||
appContext: { components: {} },
|
||||
components: {},
|
||||
parent: null,
|
||||
};
|
||||
|
||||
const mockVueComponent = {
|
||||
props: { params: Object, api: Object, containerApi: Object, title: String },
|
||||
template: '<div>{{ title }}: Test Paneview Panel</div>',
|
||||
} as any;
|
||||
|
||||
const panelView = new VuePaneviewPanelView(
|
||||
'paneview-test-id',
|
||||
mockVueComponent,
|
||||
mockVueInstance as any
|
||||
);
|
||||
|
||||
expect(panelView.id).toBe('paneview-test-id');
|
||||
expect(panelView.element).toBeInstanceOf(HTMLElement);
|
||||
expect(typeof panelView.init).toBe('function');
|
||||
expect(typeof panelView.update).toBe('function');
|
||||
expect(typeof panelView.dispose).toBe('function');
|
||||
expect(typeof panelView.toJSON).toBe('function');
|
||||
});
|
||||
|
||||
test('should return correct JSON representation', () => {
|
||||
const mockVueInstance = {
|
||||
appContext: { components: {} },
|
||||
components: {},
|
||||
parent: null,
|
||||
};
|
||||
|
||||
const panelView = new VuePaneviewPanelView(
|
||||
'paneview-test-id',
|
||||
{ template: '<div>Test</div>' } as any,
|
||||
mockVueInstance as any
|
||||
);
|
||||
|
||||
const json = panelView.toJSON();
|
||||
expect(json).toEqual({ id: 'paneview-test-id' });
|
||||
});
|
||||
|
||||
test('should handle lifecycle methods gracefully', () => {
|
||||
const mockVueInstance = {
|
||||
appContext: { components: {} },
|
||||
components: {},
|
||||
parent: null,
|
||||
};
|
||||
|
||||
const panelView = new VuePaneviewPanelView(
|
||||
'test-id',
|
||||
{ template: '<div>Test</div>' } as any,
|
||||
mockVueInstance as any
|
||||
);
|
||||
|
||||
expect(() => panelView.update({ params: { data: 'test' } })).not.toThrow();
|
||||
expect(() => panelView.dispose()).not.toThrow();
|
||||
});
|
||||
});
|
||||
76
packages/dockview-vue/src/__tests__/simple.spec.ts
Normal file
76
packages/dockview-vue/src/__tests__/simple.spec.ts
Normal file
@ -0,0 +1,76 @@
|
||||
// Import core functionality that we know works
|
||||
import * as core from 'dockview-core';
|
||||
|
||||
// Simple unit tests that verify basic functionality without complex Vue component testing
|
||||
describe('Vue Components Basic Tests', () => {
|
||||
test('should be able to import core dockview functionality', () => {
|
||||
expect(core.createDockview).toBeDefined();
|
||||
expect(core.createSplitview).toBeDefined();
|
||||
expect(core.createGridview).toBeDefined();
|
||||
expect(core.createPaneview).toBeDefined();
|
||||
});
|
||||
|
||||
test('should be able to import APIs', () => {
|
||||
expect(core.DockviewApi).toBeDefined();
|
||||
expect(core.SplitviewApi).toBeDefined();
|
||||
expect(core.GridviewApi).toBeDefined();
|
||||
expect(core.PaneviewApi).toBeDefined();
|
||||
});
|
||||
|
||||
test('should be able to import orientation enum', () => {
|
||||
expect(core.Orientation).toBeDefined();
|
||||
expect(core.Orientation.HORIZONTAL).toBeDefined();
|
||||
expect(core.Orientation.VERTICAL).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// Test view classes - basic import test
|
||||
describe('Vue View Classes', () => {
|
||||
test('Vue view classes should be importable', () => {
|
||||
// Just test that we can import them without errors
|
||||
expect(() => {
|
||||
require('../splitview/view');
|
||||
require('../gridview/view');
|
||||
require('../paneview/view');
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
// Test utility functions
|
||||
describe('Utility Functions', () => {
|
||||
test('should export utility functions', () => {
|
||||
const utils = require('../utils');
|
||||
|
||||
expect(utils.findComponent).toBeDefined();
|
||||
expect(utils.mountVueComponent).toBeDefined();
|
||||
expect(utils.VuePart).toBeDefined();
|
||||
|
||||
expect(typeof utils.findComponent).toBe('function');
|
||||
expect(typeof utils.mountVueComponent).toBe('function');
|
||||
expect(typeof utils.VuePart).toBe('function');
|
||||
});
|
||||
|
||||
test('findComponent should throw when component not found', () => {
|
||||
const { findComponent } = require('../utils');
|
||||
|
||||
const mockInstance = {
|
||||
components: {},
|
||||
parent: null,
|
||||
appContext: {
|
||||
components: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => findComponent(mockInstance, 'non-existent')).toThrow(
|
||||
"Failed to find Vue Component 'non-existent'"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Test that the package builds correctly
|
||||
describe('Package Structure', () => {
|
||||
test('package should build without errors', () => {
|
||||
// If we get this far, the package structure is correct
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
124
packages/dockview-vue/src/__tests__/splitview.spec.ts
Normal file
124
packages/dockview-vue/src/__tests__/splitview.spec.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { createSplitview, Orientation, PROPERTY_KEYS_SPLITVIEW } from 'dockview-core';
|
||||
import { VueSplitviewPanelView } from '../splitview/view';
|
||||
|
||||
describe('SplitviewVue Component', () => {
|
||||
test('should export component types', () => {
|
||||
const types = require('../splitview/types');
|
||||
|
||||
expect(types).toBeDefined();
|
||||
expect(typeof types).toBe('object');
|
||||
});
|
||||
|
||||
test('should have access to orientation constants', () => {
|
||||
expect(Orientation.HORIZONTAL).toBeDefined();
|
||||
expect(Orientation.VERTICAL).toBeDefined();
|
||||
});
|
||||
|
||||
test('should export dockview-core functionality', () => {
|
||||
const dockviewCore = require('dockview-core');
|
||||
|
||||
expect(dockviewCore.createSplitview).toBeDefined();
|
||||
expect(dockviewCore.PROPERTY_KEYS_SPLITVIEW).toBeDefined();
|
||||
});
|
||||
|
||||
test('should have correct splitview properties', () => {
|
||||
expect(PROPERTY_KEYS_SPLITVIEW).toContain('orientation');
|
||||
expect(PROPERTY_KEYS_SPLITVIEW).toContain('proportionalLayout');
|
||||
expect(PROPERTY_KEYS_SPLITVIEW).toContain('disableAutoResizing');
|
||||
});
|
||||
|
||||
test('should create splitview with Vue framework support', () => {
|
||||
// Test that Vue-specific components can be created with proper type safety
|
||||
expect(typeof createSplitview).toBe('function');
|
||||
expect(typeof VueSplitviewPanelView).toBe('function');
|
||||
|
||||
// Test that a Vue splitview panel view can be instantiated
|
||||
const mockVueComponent = { template: '<div>Test</div>' } as any;
|
||||
const mockParent = {} as any;
|
||||
|
||||
const panelView = new VueSplitviewPanelView(
|
||||
'test-id',
|
||||
'test-component',
|
||||
mockVueComponent,
|
||||
mockParent
|
||||
);
|
||||
|
||||
expect(panelView.id).toBe('test-id');
|
||||
expect(panelView.element).toBeInstanceOf(HTMLElement);
|
||||
expect(typeof panelView.getComponent).toBe('function');
|
||||
});
|
||||
|
||||
test('should handle Vue component integration', () => {
|
||||
// Test Vue component factory creation for splitview
|
||||
const mockComponent = {
|
||||
template: '<div class="vue-splitview-panel">{{ params.title }}</div>',
|
||||
props: ['params', 'api', 'containerApi']
|
||||
};
|
||||
|
||||
expect(mockComponent.template).toContain('vue-splitview-panel');
|
||||
expect(mockComponent.props).toContain('params');
|
||||
expect(mockComponent.props).toContain('api');
|
||||
expect(mockComponent.props).toContain('containerApi');
|
||||
});
|
||||
});
|
||||
|
||||
describe('VueSplitviewPanelView', () => {
|
||||
test('should be a class that extends SplitviewPanel', () => {
|
||||
expect(VueSplitviewPanelView).toBeDefined();
|
||||
expect(typeof VueSplitviewPanelView).toBe('function');
|
||||
});
|
||||
|
||||
test('should create instance with required properties', () => {
|
||||
const mockVueInstance = {
|
||||
appContext: { components: {} },
|
||||
components: {},
|
||||
parent: null,
|
||||
};
|
||||
|
||||
const mockVueComponent = {
|
||||
props: { params: Object, api: Object, containerApi: Object },
|
||||
template: '<div>Test</div>',
|
||||
} as any;
|
||||
|
||||
const panelView = new VueSplitviewPanelView(
|
||||
'test-id',
|
||||
'test-component',
|
||||
mockVueComponent,
|
||||
mockVueInstance as any
|
||||
);
|
||||
|
||||
expect(panelView.id).toBe('test-id');
|
||||
expect(panelView.element).toBeInstanceOf(HTMLElement);
|
||||
expect(typeof panelView.getComponent).toBe('function');
|
||||
});
|
||||
|
||||
test('should handle getComponent with mocked parameters', () => {
|
||||
const mockVueInstance = {
|
||||
appContext: { components: {} },
|
||||
components: {},
|
||||
parent: null,
|
||||
};
|
||||
|
||||
const mockVueComponent = {
|
||||
props: { params: Object, api: Object, containerApi: Object },
|
||||
template: '<div>Test</div>',
|
||||
} as any;
|
||||
|
||||
const panelView = new VueSplitviewPanelView(
|
||||
'test-id',
|
||||
'test-component',
|
||||
mockVueComponent,
|
||||
mockVueInstance as any
|
||||
);
|
||||
|
||||
// Mock _params to avoid accessor error
|
||||
(panelView as any)._params = {
|
||||
params: {},
|
||||
accessor: { id: 'test-accessor' }
|
||||
};
|
||||
|
||||
const component = panelView.getComponent();
|
||||
expect(component).toBeDefined();
|
||||
expect(component.constructor.name).toBe('VuePart');
|
||||
});
|
||||
});
|
||||
125
packages/dockview-vue/src/__tests__/utils.spec.ts
Normal file
125
packages/dockview-vue/src/__tests__/utils.spec.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { VuePart, findComponent, mountVueComponent } from '../utils';
|
||||
|
||||
describe('Utils', () => {
|
||||
test('should export VuePart class', () => {
|
||||
expect(VuePart).toBeDefined();
|
||||
expect(typeof VuePart).toBe('function');
|
||||
});
|
||||
|
||||
test('should export findComponent function', () => {
|
||||
expect(findComponent).toBeDefined();
|
||||
expect(typeof findComponent).toBe('function');
|
||||
});
|
||||
|
||||
test('should export mountVueComponent function', () => {
|
||||
expect(mountVueComponent).toBeDefined();
|
||||
expect(typeof mountVueComponent).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findComponent', () => {
|
||||
test('should find component in instance components', () => {
|
||||
const testComponent = { template: '<div>Test</div>' };
|
||||
const mockInstance = {
|
||||
components: {
|
||||
'test-component': testComponent
|
||||
},
|
||||
appContext: { components: {} },
|
||||
parent: null
|
||||
} as any;
|
||||
|
||||
const found = findComponent(mockInstance, 'test-component');
|
||||
expect(found).toBe(testComponent);
|
||||
});
|
||||
|
||||
test('should find component in app context', () => {
|
||||
const testComponent = { template: '<div>Test</div>' };
|
||||
const mockInstance = {
|
||||
components: {},
|
||||
appContext: {
|
||||
components: {
|
||||
'global-component': testComponent
|
||||
}
|
||||
},
|
||||
parent: null
|
||||
} as any;
|
||||
|
||||
const found = findComponent(mockInstance, 'global-component');
|
||||
expect(found).toBe(testComponent);
|
||||
});
|
||||
|
||||
test('should throw error when component not found', () => {
|
||||
const mockInstance = {
|
||||
components: {},
|
||||
appContext: { components: {} },
|
||||
parent: null
|
||||
} as any;
|
||||
|
||||
expect(() => findComponent(mockInstance, 'non-existent')).toThrow(
|
||||
"Failed to find Vue Component 'non-existent'"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('VuePart', () => {
|
||||
let container: HTMLElement;
|
||||
let testComponent: any;
|
||||
let mockParent: any;
|
||||
let vuePart: VuePart;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.createElement('div');
|
||||
|
||||
testComponent = {
|
||||
template: '<div class="vue-part">{{ params.title }} - {{ params.data }}</div>',
|
||||
props: ['params', 'api', 'containerApi']
|
||||
};
|
||||
|
||||
mockParent = {
|
||||
appContext: {
|
||||
components: {},
|
||||
provides: {}
|
||||
},
|
||||
provides: {}
|
||||
};
|
||||
|
||||
const mockProps = {
|
||||
params: { title: 'Test Title', data: 'test data' },
|
||||
api: { id: 'test-api' },
|
||||
containerApi: { id: 'container-api' }
|
||||
};
|
||||
|
||||
vuePart = new VuePart(container, testComponent, mockParent, mockProps);
|
||||
});
|
||||
|
||||
test('should create VuePart instance', () => {
|
||||
expect(vuePart).toBeInstanceOf(VuePart);
|
||||
expect(vuePart.constructor.name).toBe('VuePart');
|
||||
});
|
||||
|
||||
test('should have required methods', () => {
|
||||
expect(typeof vuePart.init).toBe('function');
|
||||
expect(typeof vuePart.update).toBe('function');
|
||||
expect(typeof vuePart.dispose).toBe('function');
|
||||
});
|
||||
|
||||
test('should handle update before init gracefully', () => {
|
||||
expect(() => vuePart.update({ params: { title: 'New' } })).not.toThrow();
|
||||
});
|
||||
|
||||
test('should handle dispose before init gracefully', () => {
|
||||
expect(() => vuePart.dispose()).not.toThrow();
|
||||
});
|
||||
|
||||
test('should handle init call without throwing', () => {
|
||||
// Test that init can be called without throwing
|
||||
// Note: may fail due to Vue environment setup but should not crash the test
|
||||
try {
|
||||
vuePart.init();
|
||||
vuePart.dispose();
|
||||
} catch (error) {
|
||||
// Vue mounting may fail in test environment, but VuePart should handle it
|
||||
expect(error).toBeDefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
128
packages/dockview-vue/src/composables/useViewComponent.ts
Normal file
128
packages/dockview-vue/src/composables/useViewComponent.ts
Normal file
@ -0,0 +1,128 @@
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
watch,
|
||||
onBeforeUnmount,
|
||||
markRaw,
|
||||
getCurrentInstance,
|
||||
type ComponentInternalInstance,
|
||||
} from 'vue';
|
||||
import { findComponent } from '../utils';
|
||||
|
||||
export interface ViewComponentConfig<
|
||||
TApi,
|
||||
TOptions,
|
||||
TProps,
|
||||
TEvents,
|
||||
TView,
|
||||
TFrameworkOptions
|
||||
> {
|
||||
componentName: string;
|
||||
propertyKeys: readonly (keyof TOptions)[];
|
||||
createApi: (element: HTMLElement, options: TOptions & TFrameworkOptions) => TApi;
|
||||
createView: (
|
||||
id: string,
|
||||
name: string | undefined,
|
||||
component: any,
|
||||
instance: ComponentInternalInstance
|
||||
) => TView;
|
||||
extractCoreOptions: (props: TProps) => TOptions;
|
||||
}
|
||||
|
||||
export function useViewComponent<
|
||||
TApi extends { dispose(): void; updateOptions(options: Partial<TOptions>): void; layout(width: number, height: number): void },
|
||||
TOptions,
|
||||
TProps,
|
||||
TEvents,
|
||||
TView,
|
||||
TFrameworkOptions
|
||||
>(
|
||||
config: ViewComponentConfig<TApi, TOptions, TProps, TEvents, TView, TFrameworkOptions>,
|
||||
props: TProps,
|
||||
emit: (event: 'ready', payload: { api: TApi }) => void
|
||||
) {
|
||||
const el = ref<HTMLElement | null>(null);
|
||||
const instance = ref<TApi | null>(null);
|
||||
|
||||
config.propertyKeys.forEach((coreOptionKey) => {
|
||||
watch(
|
||||
() => (props as any)[coreOptionKey],
|
||||
(newValue) => {
|
||||
if (instance.value) {
|
||||
instance.value.updateOptions({ [coreOptionKey]: newValue } as Partial<TOptions>);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => (props as any).components,
|
||||
() => {
|
||||
if (instance.value) {
|
||||
const inst = getCurrentInstance();
|
||||
if (!inst) {
|
||||
throw new Error(`${config.componentName}: getCurrentInstance() returned null`);
|
||||
}
|
||||
|
||||
instance.value.updateOptions({
|
||||
createComponent: (options: { id: string; name?: string }) => {
|
||||
const component = findComponent(inst, options.name!);
|
||||
return config.createView(
|
||||
options.id,
|
||||
options.name,
|
||||
component! as any,
|
||||
inst
|
||||
);
|
||||
},
|
||||
} as unknown as Partial<TOptions>);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (!el.value) {
|
||||
throw new Error(`${config.componentName}: element is not mounted`);
|
||||
}
|
||||
|
||||
const inst = getCurrentInstance();
|
||||
|
||||
if (!inst) {
|
||||
throw new Error(`${config.componentName}: getCurrentInstance() returned null`);
|
||||
}
|
||||
|
||||
const frameworkOptions = {
|
||||
createComponent(options: { id: string; name?: string }) {
|
||||
const component = findComponent(inst, options.name!);
|
||||
return config.createView(
|
||||
options.id,
|
||||
options.name,
|
||||
component! as any,
|
||||
inst
|
||||
);
|
||||
},
|
||||
} as TFrameworkOptions;
|
||||
|
||||
const api = config.createApi(el.value, {
|
||||
...config.extractCoreOptions(props),
|
||||
...frameworkOptions,
|
||||
});
|
||||
|
||||
const { clientWidth, clientHeight } = el.value;
|
||||
api.layout(clientWidth, clientHeight);
|
||||
|
||||
instance.value = markRaw(api) as any;
|
||||
|
||||
emit('ready', { api });
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (instance.value) {
|
||||
instance.value.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
el,
|
||||
instance,
|
||||
};
|
||||
}
|
||||
41
packages/dockview-vue/src/gridview/gridview.vue
Normal file
41
packages/dockview-vue/src/gridview/gridview.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
GridviewApi,
|
||||
type GridviewOptions,
|
||||
PROPERTY_KEYS_GRIDVIEW,
|
||||
type GridviewFrameworkOptions,
|
||||
createGridview,
|
||||
} from 'dockview-core';
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { useViewComponent } from '../composables/useViewComponent';
|
||||
import { VueGridviewPanelView } from './view';
|
||||
import type { IGridviewVueProps, GridviewVueEvents } from './types';
|
||||
|
||||
function extractCoreOptions(props: IGridviewVueProps): GridviewOptions {
|
||||
const coreOptions = (PROPERTY_KEYS_GRIDVIEW as (keyof GridviewOptions)[]).reduce(
|
||||
(obj, key) => {
|
||||
(obj as any)[key] = props[key];
|
||||
return obj;
|
||||
},
|
||||
{} as Partial<GridviewOptions>
|
||||
);
|
||||
|
||||
return coreOptions as GridviewOptions;
|
||||
}
|
||||
|
||||
const emit = defineEmits<GridviewVueEvents>();
|
||||
const props = defineProps<IGridviewVueProps>();
|
||||
|
||||
const { el } = useViewComponent({
|
||||
componentName: 'gridview-vue',
|
||||
propertyKeys: PROPERTY_KEYS_GRIDVIEW,
|
||||
createApi: createGridview,
|
||||
createView: (id, name, component, instance) =>
|
||||
new VueGridviewPanelView(id, name, component, instance),
|
||||
extractCoreOptions,
|
||||
}, props, emit);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="el" style="height: 100%; width: 100%" />
|
||||
</template>
|
||||
23
packages/dockview-vue/src/gridview/types.ts
Normal file
23
packages/dockview-vue/src/gridview/types.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import type {
|
||||
GridviewApi,
|
||||
GridviewOptions,
|
||||
GridviewPanelApi,
|
||||
} from 'dockview-core';
|
||||
|
||||
export interface GridviewReadyEvent {
|
||||
api: GridviewApi;
|
||||
}
|
||||
|
||||
export interface IGridviewVuePanelProps<T extends Record<string, any> = any> {
|
||||
params: T;
|
||||
api: GridviewPanelApi;
|
||||
containerApi: GridviewApi;
|
||||
}
|
||||
|
||||
export interface IGridviewVueProps extends GridviewOptions {
|
||||
components: Record<string, string>;
|
||||
}
|
||||
|
||||
export type GridviewVueEvents = {
|
||||
ready: [event: GridviewReadyEvent];
|
||||
};
|
||||
34
packages/dockview-vue/src/gridview/view.ts
Normal file
34
packages/dockview-vue/src/gridview/view.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import {
|
||||
GridviewApi,
|
||||
GridviewPanel,
|
||||
IFrameworkPart,
|
||||
} from 'dockview-core';
|
||||
import { type ComponentInternalInstance } from 'vue';
|
||||
import { VuePart, type VueComponent } from '../utils';
|
||||
import type { IGridviewVuePanelProps } from './types';
|
||||
|
||||
export class VueGridviewPanelView extends GridviewPanel {
|
||||
constructor(
|
||||
id: string,
|
||||
component: string,
|
||||
private readonly vueComponent: VueComponent<IGridviewVuePanelProps>,
|
||||
private readonly parent: ComponentInternalInstance
|
||||
) {
|
||||
super(id, component);
|
||||
}
|
||||
|
||||
getComponent(): IFrameworkPart {
|
||||
return new VuePart(
|
||||
this.element,
|
||||
this.vueComponent,
|
||||
this.parent,
|
||||
{
|
||||
params: this._params?.params ?? {},
|
||||
api: this.api,
|
||||
containerApi: new GridviewApi(
|
||||
(this._params as any).accessor
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,15 @@
|
||||
export * from 'dockview-core';
|
||||
|
||||
import DockviewVue from './dockview/dockview.vue';
|
||||
export { DockviewVue };
|
||||
import SplitviewVue from './splitview/splitview.vue';
|
||||
import GridviewVue from './gridview/gridview.vue';
|
||||
import PaneviewVue from './paneview/paneview.vue';
|
||||
|
||||
export { DockviewVue, SplitviewVue, GridviewVue, PaneviewVue };
|
||||
export * from './dockview/dockview.vue';
|
||||
export * from './dockview/types';
|
||||
export * from './splitview/types';
|
||||
export * from './gridview/types';
|
||||
export * from './paneview/types';
|
||||
export * from './utils';
|
||||
export type { VueComponent } from './utils';
|
||||
|
||||
41
packages/dockview-vue/src/paneview/paneview.vue
Normal file
41
packages/dockview-vue/src/paneview/paneview.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
PaneviewApi,
|
||||
type PaneviewOptions,
|
||||
PROPERTY_KEYS_PANEVIEW,
|
||||
type PaneviewFrameworkOptions,
|
||||
createPaneview,
|
||||
} from 'dockview-core';
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { useViewComponent } from '../composables/useViewComponent';
|
||||
import { VuePaneviewPanelView } from './view';
|
||||
import type { IPaneviewVueProps, PaneviewVueEvents } from './types';
|
||||
|
||||
function extractCoreOptions(props: IPaneviewVueProps): PaneviewOptions {
|
||||
const coreOptions = (PROPERTY_KEYS_PANEVIEW as (keyof PaneviewOptions)[]).reduce(
|
||||
(obj, key) => {
|
||||
(obj as any)[key] = props[key];
|
||||
return obj;
|
||||
},
|
||||
{} as Partial<PaneviewOptions>
|
||||
);
|
||||
|
||||
return coreOptions as PaneviewOptions;
|
||||
}
|
||||
|
||||
const emit = defineEmits<PaneviewVueEvents>();
|
||||
const props = defineProps<IPaneviewVueProps>();
|
||||
|
||||
const { el } = useViewComponent({
|
||||
componentName: 'paneview-vue',
|
||||
propertyKeys: PROPERTY_KEYS_PANEVIEW,
|
||||
createApi: createPaneview,
|
||||
createView: (id, name, component, instance) =>
|
||||
new VuePaneviewPanelView(id, component, instance),
|
||||
extractCoreOptions,
|
||||
}, props, emit);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="el" style="height: 100%; width: 100%" />
|
||||
</template>
|
||||
24
packages/dockview-vue/src/paneview/types.ts
Normal file
24
packages/dockview-vue/src/paneview/types.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type {
|
||||
PaneviewApi,
|
||||
PaneviewOptions,
|
||||
PaneviewPanelApi,
|
||||
} from 'dockview-core';
|
||||
|
||||
export interface PaneviewReadyEvent {
|
||||
api: PaneviewApi;
|
||||
}
|
||||
|
||||
export interface IPaneviewVuePanelProps<T extends Record<string, any> = any> {
|
||||
params: T;
|
||||
api: PaneviewPanelApi;
|
||||
containerApi: PaneviewApi;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface IPaneviewVueProps extends PaneviewOptions {
|
||||
components: Record<string, string>;
|
||||
}
|
||||
|
||||
export type PaneviewVueEvents = {
|
||||
ready: [event: PaneviewReadyEvent];
|
||||
};
|
||||
58
packages/dockview-vue/src/paneview/view.ts
Normal file
58
packages/dockview-vue/src/paneview/view.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import {
|
||||
IPanePart,
|
||||
PanePanelComponentInitParameter,
|
||||
PanelUpdateEvent,
|
||||
} from 'dockview-core';
|
||||
import { type ComponentInternalInstance } from 'vue';
|
||||
import { VuePart, type VueComponent } from '../utils';
|
||||
import type { IPaneviewVuePanelProps } from './types';
|
||||
|
||||
export class VuePaneviewPanelView implements IPanePart {
|
||||
private readonly _element: HTMLElement;
|
||||
private part?: VuePart<IPaneviewVuePanelProps>;
|
||||
|
||||
get element() {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
private readonly vueComponent: VueComponent<IPaneviewVuePanelProps>,
|
||||
private readonly parent: ComponentInternalInstance
|
||||
) {
|
||||
this._element = document.createElement('div');
|
||||
this._element.style.height = '100%';
|
||||
this._element.style.width = '100%';
|
||||
}
|
||||
|
||||
public init(parameters: PanePanelComponentInitParameter): void {
|
||||
this.part = new VuePart(
|
||||
this.element,
|
||||
this.vueComponent,
|
||||
this.parent,
|
||||
{
|
||||
params: parameters.params,
|
||||
api: parameters.api,
|
||||
title: parameters.title,
|
||||
containerApi: parameters.containerApi,
|
||||
}
|
||||
);
|
||||
this.part.init();
|
||||
}
|
||||
|
||||
public toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
};
|
||||
}
|
||||
|
||||
public update(params: PanelUpdateEvent) {
|
||||
// The update method for paneview doesn't need to pass all props,
|
||||
// just the updated params
|
||||
(this.part as any)?.update({ params: params.params });
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.part?.dispose();
|
||||
}
|
||||
}
|
||||
41
packages/dockview-vue/src/splitview/splitview.vue
Normal file
41
packages/dockview-vue/src/splitview/splitview.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
SplitviewApi,
|
||||
type SplitviewOptions,
|
||||
PROPERTY_KEYS_SPLITVIEW,
|
||||
type SplitviewFrameworkOptions,
|
||||
createSplitview,
|
||||
} from 'dockview-core';
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { useViewComponent } from '../composables/useViewComponent';
|
||||
import { VueSplitviewPanelView } from './view';
|
||||
import type { ISplitviewVueProps, SplitviewVueEvents } from './types';
|
||||
|
||||
function extractCoreOptions(props: ISplitviewVueProps): SplitviewOptions {
|
||||
const coreOptions = (PROPERTY_KEYS_SPLITVIEW as (keyof SplitviewOptions)[]).reduce(
|
||||
(obj, key) => {
|
||||
(obj as any)[key] = props[key];
|
||||
return obj;
|
||||
},
|
||||
{} as Partial<SplitviewOptions>
|
||||
);
|
||||
|
||||
return coreOptions as SplitviewOptions;
|
||||
}
|
||||
|
||||
const emit = defineEmits<SplitviewVueEvents>();
|
||||
const props = defineProps<ISplitviewVueProps>();
|
||||
|
||||
const { el } = useViewComponent({
|
||||
componentName: 'splitview-vue',
|
||||
propertyKeys: PROPERTY_KEYS_SPLITVIEW,
|
||||
createApi: createSplitview,
|
||||
createView: (id, name, component, instance) =>
|
||||
new VueSplitviewPanelView(id, name, component, instance),
|
||||
extractCoreOptions,
|
||||
}, props, emit);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="el" style="height: 100%; width: 100%" />
|
||||
</template>
|
||||
23
packages/dockview-vue/src/splitview/types.ts
Normal file
23
packages/dockview-vue/src/splitview/types.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import type {
|
||||
SplitviewApi,
|
||||
SplitviewOptions,
|
||||
SplitviewPanelApi,
|
||||
} from 'dockview-core';
|
||||
|
||||
export interface SplitviewReadyEvent {
|
||||
api: SplitviewApi;
|
||||
}
|
||||
|
||||
export interface ISplitviewVuePanelProps<T extends Record<string, any> = any> {
|
||||
params: T;
|
||||
api: SplitviewPanelApi;
|
||||
containerApi: SplitviewApi;
|
||||
}
|
||||
|
||||
export interface ISplitviewVueProps extends SplitviewOptions {
|
||||
components: Record<string, string>;
|
||||
}
|
||||
|
||||
export type SplitviewVueEvents = {
|
||||
ready: [event: SplitviewReadyEvent];
|
||||
};
|
||||
35
packages/dockview-vue/src/splitview/view.ts
Normal file
35
packages/dockview-vue/src/splitview/view.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import {
|
||||
SplitviewApi,
|
||||
PanelViewInitParameters,
|
||||
SplitviewPanel,
|
||||
IFrameworkPart,
|
||||
} from 'dockview-core';
|
||||
import { type ComponentInternalInstance } from 'vue';
|
||||
import { VuePart, type VueComponent } from '../utils';
|
||||
import type { ISplitviewVuePanelProps } from './types';
|
||||
|
||||
export class VueSplitviewPanelView extends SplitviewPanel {
|
||||
constructor(
|
||||
id: string,
|
||||
component: string,
|
||||
private readonly vueComponent: VueComponent<ISplitviewVuePanelProps>,
|
||||
private readonly parent: ComponentInternalInstance
|
||||
) {
|
||||
super(id, component);
|
||||
}
|
||||
|
||||
getComponent(): IFrameworkPart {
|
||||
return new VuePart(
|
||||
this.element,
|
||||
this.vueComponent,
|
||||
this.parent,
|
||||
{
|
||||
params: this._params?.params ?? {},
|
||||
api: this.api,
|
||||
containerApi: new SplitviewApi(
|
||||
(this._params as any).accessor
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -41,7 +41,7 @@ export function findComponent(
|
||||
name: string
|
||||
): VueComponent | null {
|
||||
let instance = parent as any;
|
||||
let component = null;
|
||||
let component: any = null;
|
||||
|
||||
while (!component && instance) {
|
||||
component = instance.components?.[name];
|
||||
@ -56,7 +56,7 @@ export function findComponent(
|
||||
throw new Error(`Failed to find Vue Component '${name}'`);
|
||||
}
|
||||
|
||||
return component;
|
||||
return component as VueComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -231,3 +231,35 @@ export class VueHeaderActionsRenderer
|
||||
this._renderDisposable?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class VuePart<T extends Record<string, any> = any> {
|
||||
private _renderDisposable:
|
||||
| { update: (props: any) => void; dispose: () => void }
|
||||
| undefined;
|
||||
|
||||
constructor(
|
||||
private readonly element: HTMLElement,
|
||||
private readonly vueComponent: VueComponent<T>,
|
||||
private readonly parent: ComponentInternalInstance,
|
||||
private props: T
|
||||
) {}
|
||||
|
||||
init(): void {
|
||||
this._renderDisposable?.dispose();
|
||||
this._renderDisposable = mountVueComponent(
|
||||
this.vueComponent,
|
||||
this.parent,
|
||||
this.props,
|
||||
this.element
|
||||
);
|
||||
}
|
||||
|
||||
update(props: T): void {
|
||||
this.props = { ...this.props, ...props };
|
||||
this._renderDisposable?.update(this.props);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._renderDisposable?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview",
|
||||
"version": "4.7.1",
|
||||
"version": "4.11.0",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
@ -53,7 +53,7 @@
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"dockview-core": "^4.7.1"
|
||||
"dockview-core": "^4.11.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
|
||||
@ -10,6 +10,11 @@ function useTitle(api: DockviewPanelApi): string | undefined {
|
||||
setTitle(event.title);
|
||||
});
|
||||
|
||||
// Depending on the order in which React effects are run, the title may already be out of sync (cf. issue #1003).
|
||||
if (title !== api.title) {
|
||||
setTitle(api.title);
|
||||
}
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
|
||||
17
packages/docs/blog/2025-09-22-dockview-4.9.0.md
Normal file
17
packages/docs/blog/2025-09-22-dockview-4.9.0.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
slug: dockview-4.8.0-release
|
||||
title: Dockview 4.8.0
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- Angular framework wrapper support [#1001](https://github.com/mathuo/dockview/pull/1001)
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
## 🔥 Breaking changes
|
||||
22
packages/docs/blog/2025-10-30-dockview-4.10.0.md
Normal file
22
packages/docs/blog/2025-10-30-dockview-4.10.0.md
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
slug: dockview-4.10.0-release
|
||||
title: Dockview 4.10.0
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- Template docs [#1027](https://github.com/mathuo/dockview/pull/1027)
|
||||
- Angular docs
|
||||
- Enhanced Vue component tests with comprehensive DOM and API testing [#1024](https://github.com/mathuo/dockview/pull/1024)
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Bug: Fix registerPanel timing to prevent undefined API errors [#1026](https://github.com/mathuo/dockview/pull/1026)
|
||||
- Bug: Fix title possibly out of sync in DockviewDefaultTab [#1015](https://github.com/mathuo/dockview/pull/1015)
|
||||
|
||||
## 🔥 Breaking changes
|
||||
17
packages/docs/blog/2025-11-04-dockview-4.11.0.md
Normal file
17
packages/docs/blog/2025-11-04-dockview-4.11.0.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
slug: dockview-4.11.0-release
|
||||
title: Dockview 4.11.0
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Bug: Fix constraints persistence and precedence issues [#967](https://github.com/mathuo/dockview/pull/967)
|
||||
|
||||
## 🔥 Breaking changes
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-docs",
|
||||
"version": "4.8.0",
|
||||
"version": "4.11.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "npm run build-templates && docusaurus build",
|
||||
@ -38,7 +38,7 @@
|
||||
"ag-grid-react": "^31.0.2",
|
||||
"axios": "^1.6.3",
|
||||
"clsx": "^2.1.0",
|
||||
"dockview": "^4.7.1",
|
||||
"dockview": "^4.11.0",
|
||||
"prism-react-renderer": "^2.3.1",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-laag": "^2.0.5",
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { argv } from 'process';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
@ -84,12 +85,13 @@ const IMPORTS_PATHS = {
|
||||
},
|
||||
typescript: {},
|
||||
angular: {
|
||||
'@angular/core': `https://esm.sh/@angular/core@${ANGULAR_VERSION}`,
|
||||
'@angular/common': `https://esm.sh/@angular/common@${ANGULAR_VERSION}`,
|
||||
'@angular/platform-browser-dynamic': `https://esm.sh/@angular/platform-browser-dynamic@${ANGULAR_VERSION}`,
|
||||
'@angular/platform-browser': `https://esm.sh/@angular/platform-browser@${ANGULAR_VERSION}`,
|
||||
'rxjs': `https://esm.sh/rxjs@7.8.1`,
|
||||
'zone.js': `https://esm.sh/zone.js@0.14.3`,
|
||||
'@angular/core': `https://esm.sh/@angular/core@${ANGULAR_VERSION}?target=es2018`,
|
||||
'@angular/common': `https://esm.sh/@angular/common@${ANGULAR_VERSION}?target=es2018`,
|
||||
'@angular/platform-browser-dynamic': `https://esm.sh/@angular/platform-browser-dynamic@${ANGULAR_VERSION}?target=es2018`,
|
||||
'@angular/platform-browser': `https://esm.sh/@angular/platform-browser@${ANGULAR_VERSION}?target=es2018`,
|
||||
'@angular/compiler': `https://esm.sh/@angular/compiler@${ANGULAR_VERSION}?target=es2018`,
|
||||
rxjs: `https://esm.sh/rxjs@7.8.1?target=es2018`,
|
||||
'zone.js': `https://esm.sh/zone.js@0.14.3?target=es2018`,
|
||||
},
|
||||
};
|
||||
|
||||
@ -97,6 +99,54 @@ const template = fs
|
||||
.readFileSync(path.join(__dirname, './template.html'))
|
||||
.toString();
|
||||
|
||||
async function compileAngularTypeScript(inputPath, outputPath) {
|
||||
try {
|
||||
// Import TypeScript compiler
|
||||
const ts = await import('typescript');
|
||||
|
||||
// Copy directory structure first
|
||||
fs.copySync(inputPath, outputPath);
|
||||
|
||||
// Find all .ts files and transpile them
|
||||
const tsFiles = fs
|
||||
.readdirSync(outputPath)
|
||||
.filter((file) => file.endsWith('.ts'));
|
||||
|
||||
for (const file of tsFiles) {
|
||||
const filePath = path.join(outputPath, file);
|
||||
const source = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Transpile with decorator support, keeping imports as-is
|
||||
const result = ts.default.transpile(source, {
|
||||
target: ts.default.ScriptTarget.ES5, // Use ES5 to ensure maximum compatibility
|
||||
module: ts.default.ModuleKind.System,
|
||||
experimentalDecorators: true,
|
||||
emitDecoratorMetadata: true,
|
||||
esModuleInterop: true,
|
||||
allowSyntheticDefaultImports: true,
|
||||
strict: false,
|
||||
skipLibCheck: true,
|
||||
noResolve: true, // Don't try to resolve imports
|
||||
useDefineForClassFields: false, // Avoid class field initialization issues
|
||||
downlevelIteration: true, // Ensure iterators work in older JS
|
||||
});
|
||||
|
||||
// Write the transpiled file
|
||||
fs.writeFileSync(filePath, result);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Failed to compile Angular TypeScript for ${inputPath}:`,
|
||||
error.message
|
||||
);
|
||||
// Fall back to copying source files as-is
|
||||
fs.copySync(inputPath, outputPath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function createIndexHTML(options) {
|
||||
return template
|
||||
.replace('{{title}}', options.title)
|
||||
@ -107,80 +157,113 @@ function createIndexHTML(options) {
|
||||
.join(',\n')}`
|
||||
)
|
||||
.replace('{{app}}', options.app)
|
||||
.replace('{{appElement}}', options.appElement || '')
|
||||
.replace('{{githubLink}}', options.githubUrl)
|
||||
.replace('{{codeSandboxLink}}', options.codeSandboxUrl)
|
||||
.replace('{{codeSandboxLink}}', options.codeSandboxUrl);
|
||||
}
|
||||
|
||||
const input_dir = path.join(__dirname, '../templates');
|
||||
const output = path.join(__dirname, '../static/templates');
|
||||
|
||||
const COMPONENTS = ['dockview'];
|
||||
const COMPONENTS = ['dockview', 'splitview', 'gridview', 'paneview'];
|
||||
const FRAMEWORKS = ['react', 'vue', 'typescript', 'angular'];
|
||||
|
||||
const list = [];
|
||||
|
||||
const githubUrl = "https://github.com/mathuo/dockview/tree/master/packages/docs/templates"
|
||||
const codeSandboxUrl = "https://codesandbox.io/p/sandbox/github/mathuo/dockview/tree/gh-pages/templates"
|
||||
const githubUrl =
|
||||
'https://github.com/mathuo/dockview/tree/master/packages/docs/templates';
|
||||
const codeSandboxUrl =
|
||||
'https://codesandbox.io/p/sandbox/github/mathuo/dockview/tree/gh-pages/templates';
|
||||
|
||||
for (const component of COMPONENTS) {
|
||||
const componentDir = path.join(input_dir, component);
|
||||
async function buildTemplates() {
|
||||
for (const component of COMPONENTS) {
|
||||
const componentDir = path.join(input_dir, component);
|
||||
|
||||
const templates = fs.readdirSync(componentDir);
|
||||
const templates = fs.readdirSync(componentDir);
|
||||
|
||||
for (const folder of templates) {
|
||||
for (const framework of FRAMEWORKS) {
|
||||
if (
|
||||
!fs.existsSync(
|
||||
path.join(componentDir, folder, framework, 'src')
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
for (const folder of templates) {
|
||||
for (const framework of FRAMEWORKS) {
|
||||
if (
|
||||
!fs.existsSync(
|
||||
path.join(componentDir, folder, framework, 'src')
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const srcPath = path.join(
|
||||
componentDir,
|
||||
folder,
|
||||
framework,
|
||||
'src'
|
||||
);
|
||||
const destPath = path.join(
|
||||
output,
|
||||
component,
|
||||
folder,
|
||||
framework,
|
||||
'src'
|
||||
);
|
||||
|
||||
if (framework === 'angular') {
|
||||
// Compile Angular TypeScript files with decorator support
|
||||
await compileAngularTypeScript(srcPath, destPath);
|
||||
} else {
|
||||
// Copy other frameworks as-is
|
||||
fs.copySync(srcPath, destPath);
|
||||
}
|
||||
|
||||
const templateGithubUrl = `${githubUrl}/${component}/${folder}/${framework}/src`;
|
||||
const templateCodeSandboxUrl = `${codeSandboxUrl}/${component}/${folder}/${framework}`;
|
||||
|
||||
const template = createIndexHTML({
|
||||
title: `Dockview | ${folder} ${framework}`,
|
||||
app:
|
||||
framework === 'react'
|
||||
? './src/index.tsx'
|
||||
: framework === 'angular'
|
||||
? './src/index.ts'
|
||||
: './src/index.ts',
|
||||
appElement:
|
||||
framework === 'angular' ? '<app-root></app-root>' : '',
|
||||
importPaths: {
|
||||
...IMPORTS_PATHS[framework],
|
||||
...DOCKVIEW_CDN[framework][
|
||||
USE_LOCAL_CDN ? 'local' : 'remote'
|
||||
],
|
||||
},
|
||||
githubUrl: templateGithubUrl,
|
||||
codeSandboxUrl: templateCodeSandboxUrl,
|
||||
});
|
||||
fs.writeFileSync(
|
||||
path.join(
|
||||
output,
|
||||
component,
|
||||
folder,
|
||||
framework,
|
||||
'index.html'
|
||||
),
|
||||
template
|
||||
);
|
||||
|
||||
list.push({
|
||||
name: `${component}/${framework}/${folder}`,
|
||||
path: path.join(component, folder, framework, 'index.html'),
|
||||
});
|
||||
}
|
||||
fs.copySync(
|
||||
path.join(componentDir, folder, framework, 'src'),
|
||||
path.join(output, component, folder, framework, 'src')
|
||||
);
|
||||
|
||||
const templateGithubUrl = `${githubUrl}/${component}/${folder}/${framework}/src`
|
||||
const templateCodeSandboxUrl = `${codeSandboxUrl}/${component}/${folder}/${framework}`
|
||||
|
||||
const template = createIndexHTML({
|
||||
title: `Dockview | ${folder} ${framework}`,
|
||||
app:
|
||||
framework === 'react'
|
||||
? './src/index.tsx'
|
||||
: framework === 'angular'
|
||||
? './src/index.ts'
|
||||
: './src/index.ts',
|
||||
importPaths: {
|
||||
...IMPORTS_PATHS[framework],
|
||||
...DOCKVIEW_CDN[framework][
|
||||
USE_LOCAL_CDN ? 'local' : 'remote'
|
||||
],
|
||||
},
|
||||
githubUrl: templateGithubUrl,
|
||||
codeSandboxUrl: templateCodeSandboxUrl
|
||||
});
|
||||
fs.writeFileSync(
|
||||
path.join(output, component, folder, framework, 'index.html'),
|
||||
template
|
||||
);
|
||||
|
||||
list.push({
|
||||
name: `${framework}/${folder}`,
|
||||
path: path.join(component, folder, framework, 'index.html'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const index = `<div>${list
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((item) => {
|
||||
return `<div><a href=${`/templates/${item.path}`}>${
|
||||
item.name
|
||||
}</a></div>`;
|
||||
})
|
||||
.join('\n')}</div>`;
|
||||
|
||||
fs.writeFileSync(path.join(output, 'index.html'), index);
|
||||
}
|
||||
|
||||
const index = `<div>${list
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((item) => {
|
||||
return `<div><a href=${`/templates/${item.path}`}>${
|
||||
item.name
|
||||
}</a></div>`;
|
||||
})
|
||||
.join('\n')}</div>`;
|
||||
|
||||
fs.writeFileSync(path.join(output, 'index.html'), index);
|
||||
// Run the build
|
||||
buildTemplates().catch(console.error);
|
||||
|
||||
@ -96,7 +96,7 @@
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
<div id="app">{{appElement}}</div>
|
||||
</div>
|
||||
<script type="systemjs-module" src="import:{{app}}"></script>
|
||||
<object
|
||||
|
||||
@ -1,17 +1,22 @@
|
||||
import 'zone.js';
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { Component, Type } from '@angular/core';
|
||||
import { DockviewAngularComponent } from 'dockview-angular';
|
||||
import '@angular/compiler';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { Component, Type, NgModule, Input } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { DockviewAngularModule } from 'dockview-angular';
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
|
||||
// Default panel component
|
||||
@Component({
|
||||
selector: 'default-panel',
|
||||
template: `<div>{{ api.title }}</div>`,
|
||||
standalone: true,
|
||||
template: `<div>{{ title || 'Default Panel' }}</div>`
|
||||
})
|
||||
export class DefaultPanelComponent {
|
||||
api: any;
|
||||
@Input() api: any;
|
||||
|
||||
get title() {
|
||||
return this.api?.title || this.api?.id || 'Panel';
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
@ -27,9 +32,7 @@ export class DefaultPanelComponent {
|
||||
(ready)="onReady($event)">
|
||||
</dv-dockview>
|
||||
</div>
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [DockviewAngularComponent]
|
||||
`
|
||||
})
|
||||
export class AppComponent {
|
||||
components: Record<string, Type<any>>;
|
||||
@ -78,5 +81,14 @@ export class AppComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// Bootstrap the application
|
||||
bootstrapApplication(AppComponent).catch(err => console.error(err));
|
||||
// App module
|
||||
@NgModule({
|
||||
declarations: [AppComponent, DefaultPanelComponent],
|
||||
imports: [BrowserModule, DockviewAngularModule],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
// Bootstrap the application with JIT compilation
|
||||
platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err));
|
||||
@ -42,7 +42,7 @@ export const App: React.FC = (props: { theme?: string }) => {
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
title: 'Panel 2',
|
||||
position: { referencePanel: panel },
|
||||
position: { referencePanel: panel.id },
|
||||
});
|
||||
|
||||
const panel3 = event.api.addPanel({
|
||||
@ -50,14 +50,14 @@ export const App: React.FC = (props: { theme?: string }) => {
|
||||
component: 'default',
|
||||
title: 'Panel 3',
|
||||
|
||||
position: { referencePanel: panel, direction: 'right' },
|
||||
position: { referencePanel: panel.id, direction: 'right' },
|
||||
});
|
||||
|
||||
const panel4 = event.api.addPanel({
|
||||
id: 'panel_4',
|
||||
component: 'default',
|
||||
title: 'Panel 4',
|
||||
position: { referencePanel: panel3 },
|
||||
position: { referencePanel: panel3.id },
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
76
packages/docs/templates/gridview/basic/angular/src/index.ts
vendored
Normal file
76
packages/docs/templates/gridview/basic/angular/src/index.ts
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
import 'zone.js';
|
||||
import '@angular/compiler';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { Component, Type, NgModule, Input } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { DockviewAngularModule } from 'dockview-angular';
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
|
||||
@Component({
|
||||
selector: 'default-panel',
|
||||
template: `<div style="padding: 10px; color: white; background: #1e1e1e; border: 1px solid #333; height: 100%;">Panel {{ api?.id || 'Unknown' }}</div>`
|
||||
})
|
||||
export class DefaultPanelComponent {
|
||||
@Input() api: any;
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<div style="height: 100vh;">
|
||||
<dv-gridview
|
||||
[components]="components"
|
||||
className="dockview-theme-abyss"
|
||||
(ready)="onReady($event)">
|
||||
</dv-gridview>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class AppComponent {
|
||||
components: Record<string, Type<any>>;
|
||||
|
||||
constructor() {
|
||||
this.components = {
|
||||
default: DefaultPanelComponent,
|
||||
};
|
||||
}
|
||||
|
||||
onReady(event: any) {
|
||||
const api = event.api;
|
||||
|
||||
const panel1 = api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel2 = api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
position: { referencePanel: panel1.id, direction: 'right' },
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
position: { referencePanel: panel1.id, direction: 'below' },
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_4',
|
||||
component: 'default',
|
||||
position: { referencePanel: panel2.id, direction: 'below' },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent, DefaultPanelComponent],
|
||||
imports: [BrowserModule, DockviewAngularModule],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err));
|
||||
59
packages/docs/templates/gridview/basic/react/src/app.tsx
vendored
Normal file
59
packages/docs/templates/gridview/basic/react/src/app.tsx
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
import {
|
||||
GridviewReact,
|
||||
GridviewReadyEvent,
|
||||
IGridviewPanelProps,
|
||||
} from 'dockview';
|
||||
import React from 'react';
|
||||
|
||||
const Default = (props: IGridviewPanelProps) => {
|
||||
return (
|
||||
<div style={{
|
||||
padding: '10px',
|
||||
color: 'white',
|
||||
background: '#1e1e1e',
|
||||
border: '1px solid #333',
|
||||
height: '100%'
|
||||
}}>
|
||||
Panel {props.api.id}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const components = {
|
||||
default: Default,
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const onReady = (event: GridviewReadyEvent) => {
|
||||
const panel1 = event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel2 = event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
position: { referencePanel: panel1, direction: 'right' },
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
position: { referencePanel: panel1, direction: 'below' },
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_4',
|
||||
component: 'default',
|
||||
position: { referencePanel: panel2, direction: 'below' },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<GridviewReact
|
||||
className={'dockview-theme-abyss'}
|
||||
onReady={onReady}
|
||||
components={components}
|
||||
/>
|
||||
);
|
||||
};
|
||||
7
packages/docs/templates/gridview/basic/react/src/index.tsx
vendored
Normal file
7
packages/docs/templates/gridview/basic/react/src/index.tsx
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './app';
|
||||
|
||||
const container = document.getElementById('app')!;
|
||||
const root = createRoot(container);
|
||||
root.render(<App />);
|
||||
60
packages/docs/templates/gridview/basic/typescript/src/index.ts
vendored
Normal file
60
packages/docs/templates/gridview/basic/typescript/src/index.ts
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import {
|
||||
createGridview,
|
||||
IGridviewPanelProps,
|
||||
IContentRenderer,
|
||||
themeAbyss,
|
||||
} from 'dockview-core';
|
||||
|
||||
class Panel implements IContentRenderer {
|
||||
private readonly _element: HTMLElement;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this._element = document.createElement('div');
|
||||
this._element.style.color = 'white';
|
||||
this._element.style.padding = '10px';
|
||||
this._element.style.background = '#1e1e1e';
|
||||
this._element.style.border = '1px solid #333';
|
||||
}
|
||||
|
||||
init(parameters: IGridviewPanelProps): void {
|
||||
this._element.textContent = `Panel ${parameters.id}`;
|
||||
}
|
||||
}
|
||||
|
||||
const api = createGridview(document.getElementById('app'), {
|
||||
theme: themeAbyss,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new Panel();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const panel1 = api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel2 = api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
position: { referencePanel: panel1.id, direction: 'right' },
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
position: { referencePanel: panel1.id, direction: 'below' },
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_4',
|
||||
component: 'default',
|
||||
position: { referencePanel: panel2.id, direction: 'below' },
|
||||
});
|
||||
76
packages/docs/templates/gridview/basic/vue/src/index.ts
vendored
Normal file
76
packages/docs/templates/gridview/basic/vue/src/index.ts
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import {
|
||||
GridviewVue,
|
||||
GridviewReadyEvent,
|
||||
IGridviewPanelProps,
|
||||
} from 'dockview-vue';
|
||||
|
||||
const Panel = defineComponent({
|
||||
name: 'Panel',
|
||||
props: {
|
||||
params: {
|
||||
type: Object as PropType<IGridviewPanelProps>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: '',
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.id = this.params.api.id;
|
||||
},
|
||||
template: `
|
||||
<div style="height: 100%; padding: 10px; color: white; background: #1e1e1e; border: 1px solid #333;">
|
||||
Panel {{ id }}
|
||||
</div>`,
|
||||
});
|
||||
|
||||
const App = defineComponent({
|
||||
name: 'App',
|
||||
components: {
|
||||
'gridview-vue': GridviewVue,
|
||||
panel: Panel,
|
||||
},
|
||||
methods: {
|
||||
onReady(event: GridviewReadyEvent) {
|
||||
const panel1 = event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'panel',
|
||||
});
|
||||
|
||||
const panel2 = event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'panel',
|
||||
position: { referencePanel: panel1, direction: 'right' },
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'panel',
|
||||
position: { referencePanel: panel1, direction: 'below' },
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_4',
|
||||
component: 'panel',
|
||||
position: { referencePanel: panel2, direction: 'below' },
|
||||
});
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<gridview-vue
|
||||
style="width: 100%; height: 100%"
|
||||
class="dockview-theme-abyss"
|
||||
@ready="onReady"
|
||||
>
|
||||
</gridview-vue>`,
|
||||
});
|
||||
|
||||
const app = createApp(App);
|
||||
app.config.errorHandler = (err) => {
|
||||
console.log(err);
|
||||
};
|
||||
app.mount(document.getElementById('app')!);
|
||||
75
packages/docs/templates/paneview/basic/angular/src/index.ts
vendored
Normal file
75
packages/docs/templates/paneview/basic/angular/src/index.ts
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
import 'zone.js';
|
||||
import '@angular/compiler';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { Component, Type, NgModule, Input } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { DockviewAngularModule } from 'dockview-angular';
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
|
||||
@Component({
|
||||
selector: 'default-panel',
|
||||
template: `<div style="padding: 10px; color: white; background: #1e1e1e; border: 1px solid #333; height: 100%;">Panel {{ api?.id || 'Unknown' }}</div>`
|
||||
})
|
||||
export class DefaultPanelComponent {
|
||||
@Input() api: any;
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<div style="height: 100vh;">
|
||||
<dv-paneview
|
||||
[components]="components"
|
||||
className="dockview-theme-abyss"
|
||||
orientation="vertical"
|
||||
(ready)="onReady($event)">
|
||||
</dv-paneview>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class AppComponent {
|
||||
components: Record<string, Type<any>>;
|
||||
|
||||
constructor() {
|
||||
this.components = {
|
||||
default: DefaultPanelComponent,
|
||||
};
|
||||
}
|
||||
|
||||
onReady(event: any) {
|
||||
const api = event.api;
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
size: 150,
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
title: 'Panel 2',
|
||||
size: 200,
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
title: 'Panel 3',
|
||||
size: 180,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent, DefaultPanelComponent],
|
||||
imports: [BrowserModule, DockviewAngularModule],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err));
|
||||
58
packages/docs/templates/paneview/basic/react/src/app.tsx
vendored
Normal file
58
packages/docs/templates/paneview/basic/react/src/app.tsx
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
import {
|
||||
PaneviewReact,
|
||||
PaneviewReadyEvent,
|
||||
IPaneviewPanelProps,
|
||||
} from 'dockview';
|
||||
import React from 'react';
|
||||
|
||||
const Default = (props: IPaneviewPanelProps) => {
|
||||
return (
|
||||
<div style={{
|
||||
padding: '10px',
|
||||
color: 'white',
|
||||
background: '#1e1e1e',
|
||||
border: '1px solid #333',
|
||||
height: '100%'
|
||||
}}>
|
||||
Panel {props.api.id}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const components = {
|
||||
default: Default,
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const onReady = (event: PaneviewReadyEvent) => {
|
||||
event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
size: 150,
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
title: 'Panel 2',
|
||||
size: 200,
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
title: 'Panel 3',
|
||||
size: 180,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<PaneviewReact
|
||||
className={'dockview-theme-abyss'}
|
||||
orientation="vertical"
|
||||
onReady={onReady}
|
||||
components={components}
|
||||
/>
|
||||
);
|
||||
};
|
||||
7
packages/docs/templates/paneview/basic/react/src/index.tsx
vendored
Normal file
7
packages/docs/templates/paneview/basic/react/src/index.tsx
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './app';
|
||||
|
||||
const container = document.getElementById('app')!;
|
||||
const root = createRoot(container);
|
||||
root.render(<App />);
|
||||
59
packages/docs/templates/paneview/basic/typescript/src/index.ts
vendored
Normal file
59
packages/docs/templates/paneview/basic/typescript/src/index.ts
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import {
|
||||
createPaneview,
|
||||
IPaneviewPanelProps,
|
||||
IContentRenderer,
|
||||
themeAbyss,
|
||||
} from 'dockview-core';
|
||||
|
||||
class Panel implements IContentRenderer {
|
||||
private readonly _element: HTMLElement;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this._element = document.createElement('div');
|
||||
this._element.style.color = 'white';
|
||||
this._element.style.padding = '10px';
|
||||
this._element.style.background = '#1e1e1e';
|
||||
this._element.style.border = '1px solid #333';
|
||||
}
|
||||
|
||||
init(parameters: IPaneviewPanelProps): void {
|
||||
this._element.textContent = `Panel ${parameters.id}`;
|
||||
}
|
||||
}
|
||||
|
||||
const api = createPaneview(document.getElementById('app'), {
|
||||
theme: themeAbyss,
|
||||
orientation: 'vertical',
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new Panel();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
size: 150,
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
title: 'Panel 2',
|
||||
size: 200,
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
title: 'Panel 3',
|
||||
size: 180,
|
||||
});
|
||||
77
packages/docs/templates/paneview/basic/vue/src/index.ts
vendored
Normal file
77
packages/docs/templates/paneview/basic/vue/src/index.ts
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import {
|
||||
PaneviewVue,
|
||||
PaneviewReadyEvent,
|
||||
IPaneviewPanelProps,
|
||||
} from 'dockview-vue';
|
||||
|
||||
const Panel = defineComponent({
|
||||
name: 'Panel',
|
||||
props: {
|
||||
params: {
|
||||
type: Object as PropType<IPaneviewPanelProps>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: '',
|
||||
title: '',
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.id = this.params.api.id;
|
||||
this.title = this.params.api.title;
|
||||
},
|
||||
template: `
|
||||
<div style="height: 100%; padding: 10px; color: white; background: #1e1e1e; border: 1px solid #333;">
|
||||
Panel {{ id }}
|
||||
</div>`,
|
||||
});
|
||||
|
||||
const App = defineComponent({
|
||||
name: 'App',
|
||||
components: {
|
||||
'paneview-vue': PaneviewVue,
|
||||
panel: Panel,
|
||||
},
|
||||
methods: {
|
||||
onReady(event: PaneviewReadyEvent) {
|
||||
event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'panel',
|
||||
title: 'Panel 1',
|
||||
size: 150,
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'panel',
|
||||
title: 'Panel 2',
|
||||
size: 200,
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'panel',
|
||||
title: 'Panel 3',
|
||||
size: 180,
|
||||
});
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<paneview-vue
|
||||
style="width: 100%; height: 100%"
|
||||
class="dockview-theme-abyss"
|
||||
orientation="vertical"
|
||||
@ready="onReady"
|
||||
>
|
||||
</paneview-vue>`,
|
||||
});
|
||||
|
||||
const app = createApp(App);
|
||||
app.config.errorHandler = (err) => {
|
||||
console.log(err);
|
||||
};
|
||||
app.mount(document.getElementById('app')!);
|
||||
72
packages/docs/templates/splitview/basic/angular/src/index.ts
vendored
Normal file
72
packages/docs/templates/splitview/basic/angular/src/index.ts
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
import 'zone.js';
|
||||
import '@angular/compiler';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { Component, Type, NgModule, Input } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { DockviewAngularModule } from 'dockview-angular';
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
|
||||
@Component({
|
||||
selector: 'default-panel',
|
||||
template: `<div style="padding: 10px; color: white; background: #1e1e1e;">Panel {{ api?.id || 'Unknown' }}</div>`
|
||||
})
|
||||
export class DefaultPanelComponent {
|
||||
@Input() api: any;
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<div style="height: 100vh;">
|
||||
<dv-splitview
|
||||
[components]="components"
|
||||
className="dockview-theme-abyss"
|
||||
orientation="horizontal"
|
||||
(ready)="onReady($event)">
|
||||
</dv-splitview>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class AppComponent {
|
||||
components: Record<string, Type<any>>;
|
||||
|
||||
constructor() {
|
||||
this.components = {
|
||||
default: DefaultPanelComponent,
|
||||
};
|
||||
}
|
||||
|
||||
onReady(event: any) {
|
||||
const api = event.api;
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
size: 200,
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
size: 300,
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
size: 200,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent, DefaultPanelComponent],
|
||||
imports: [BrowserModule, DockviewAngularModule],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err));
|
||||
49
packages/docs/templates/splitview/basic/react/src/app.tsx
vendored
Normal file
49
packages/docs/templates/splitview/basic/react/src/app.tsx
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
import {
|
||||
SplitviewReact,
|
||||
SplitviewReadyEvent,
|
||||
ISplitviewPanelProps,
|
||||
} from 'dockview';
|
||||
import React from 'react';
|
||||
|
||||
const Default = (props: ISplitviewPanelProps) => {
|
||||
return (
|
||||
<div style={{ padding: '10px', color: 'white', background: '#1e1e1e' }}>
|
||||
Panel {props.api.id}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const components = {
|
||||
default: Default,
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const onReady = (event: SplitviewReadyEvent) => {
|
||||
event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
size: 200,
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
size: 300,
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
size: 200,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SplitviewReact
|
||||
className={'dockview-theme-abyss'}
|
||||
orientation="horizontal"
|
||||
onReady={onReady}
|
||||
components={components}
|
||||
/>
|
||||
);
|
||||
};
|
||||
7
packages/docs/templates/splitview/basic/react/src/index.tsx
vendored
Normal file
7
packages/docs/templates/splitview/basic/react/src/index.tsx
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './app';
|
||||
|
||||
const container = document.getElementById('app')!;
|
||||
const root = createRoot(container);
|
||||
root.render(<App />);
|
||||
55
packages/docs/templates/splitview/basic/typescript/src/index.ts
vendored
Normal file
55
packages/docs/templates/splitview/basic/typescript/src/index.ts
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import {
|
||||
createSplitview,
|
||||
ISplitviewPanelProps,
|
||||
IContentRenderer,
|
||||
themeAbyss,
|
||||
} from 'dockview-core';
|
||||
|
||||
class Panel implements IContentRenderer {
|
||||
private readonly _element: HTMLElement;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this._element = document.createElement('div');
|
||||
this._element.style.color = 'white';
|
||||
this._element.style.padding = '10px';
|
||||
this._element.style.background = '#1e1e1e';
|
||||
}
|
||||
|
||||
init(parameters: ISplitviewPanelProps): void {
|
||||
this._element.textContent = `Panel ${parameters.id}`;
|
||||
}
|
||||
}
|
||||
|
||||
const api = createSplitview(document.getElementById('app'), {
|
||||
theme: themeAbyss,
|
||||
orientation: 'horizontal',
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new Panel();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
size: 200,
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
size: 300,
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
size: 200,
|
||||
});
|
||||
72
packages/docs/templates/splitview/basic/vue/src/index.ts
vendored
Normal file
72
packages/docs/templates/splitview/basic/vue/src/index.ts
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import {
|
||||
SplitviewVue,
|
||||
SplitviewReadyEvent,
|
||||
ISplitviewPanelProps,
|
||||
} from 'dockview-vue';
|
||||
|
||||
const Panel = defineComponent({
|
||||
name: 'Panel',
|
||||
props: {
|
||||
params: {
|
||||
type: Object as PropType<ISplitviewPanelProps>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: '',
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.id = this.params.api.id;
|
||||
},
|
||||
template: `
|
||||
<div style="height: 100%; padding: 10px; color: white; background: #1e1e1e;">
|
||||
Panel {{ id }}
|
||||
</div>`,
|
||||
});
|
||||
|
||||
const App = defineComponent({
|
||||
name: 'App',
|
||||
components: {
|
||||
'splitview-vue': SplitviewVue,
|
||||
panel: Panel,
|
||||
},
|
||||
methods: {
|
||||
onReady(event: SplitviewReadyEvent) {
|
||||
event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'panel',
|
||||
size: 200,
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'panel',
|
||||
size: 300,
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'panel',
|
||||
size: 200,
|
||||
});
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<splitview-vue
|
||||
style="width: 100%; height: 100%"
|
||||
class="dockview-theme-abyss"
|
||||
orientation="horizontal"
|
||||
@ready="onReady"
|
||||
>
|
||||
</splitview-vue>`,
|
||||
});
|
||||
|
||||
const app = createApp(App);
|
||||
app.config.errorHandler = (err) => {
|
||||
console.log(err);
|
||||
};
|
||||
app.mount(document.getElementById('app')!);
|
||||
Loading…
x
Reference in New Issue
Block a user