From 15bf4bbd1fe25031e06f73037c545ddeb5a270ce Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 17 Dec 2024 21:39:01 +0000 Subject: [PATCH 01/83] chore: v2.1.0 docs --- .../docs/blog/2024-12-17-dockview-2.1.0.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 packages/docs/blog/2024-12-17-dockview-2.1.0.md diff --git a/packages/docs/blog/2024-12-17-dockview-2.1.0.md b/packages/docs/blog/2024-12-17-dockview-2.1.0.md new file mode 100644 index 000000000..7d58a65d3 --- /dev/null +++ b/packages/docs/blog/2024-12-17-dockview-2.1.0.md @@ -0,0 +1,28 @@ +--- +slug: dockview-2.1.0-release +title: Dockview 2.1.0 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +- Persist custom popout urls in layouts [#769](https://github.com/mathuo/dockview/pull/769) +- Ensure group always exists [#783](https://github.com/mathuo/dockview/pull/783) +- Serialization of maximized views [#762](https://github.com/mathuo/dockview/pull/762) +- Set `react` as an explicit peerDependency of the `dockview` package +- Make tabs container non-focusable [#761](https://github.com/mathuo/dockview/pull/761) + +## 🛠 Miscs + +- Bug: fix `setVisible` for floating groups [#755](https://github.com/mathuo/dockview/pull/755) +- Bug: fix `onDidAddGroup` event firing when adding floating groups and panels [#785](https://github.com/mathuo/dockview/pull/785) +- Documentation [#743](https://github.com/mathuo/dockview/pull/743) [#770](https://github.com/mathuo/dockview/pull/770) +- Build tooling [#789](https://github.com/mathuo/dockview/pull/789) [#759](https://github.com/mathuo/dockview/pull/759) + +## 🔥 Breaking changes + + From 755135c5ad3dccfc4b23ee07323f11e118b2ac00 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 17 Dec 2024 22:10:49 +0000 Subject: [PATCH 02/83] chore: fix demo --- .../docs/sandboxes/react/dockview/demo-dockview/src/app.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx index 6595a3fce..a22bbf159 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx @@ -208,7 +208,7 @@ const DockviewDemo = (props: { theme?: string }) => { event.api.onDidMaximizedGroupChange((event) => { addLogLine( - `Group Maximized Changed ${event.view.id} [${event.isMaximized}]` + `Group Maximized Changed ${event.group.api.id} [${event.isMaximized}]` ); }); From 13c4a65e1886453d58bae31d345c190c9e3d28f1 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 17 Dec 2024 22:17:55 +0000 Subject: [PATCH 03/83] chore(release): publish v2.1.0 --- lerna.json | 2 +- packages/dockview-angular/package.json | 4 ++-- packages/dockview-core/package.json | 2 +- packages/dockview-react/package.json | 4 ++-- packages/dockview-vue/package.json | 4 ++-- packages/dockview/package.json | 4 ++-- packages/docs/package.json | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lerna.json b/lerna.json index c1fe88e6a..828231e80 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "2.0.0", + "version": "2.1.0", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-angular/package.json b/packages/dockview-angular/package.json index cebda7271..7e7a7e54a 100644 --- a/packages/dockview-angular/package.json +++ b/packages/dockview-angular/package.json @@ -1,6 +1,6 @@ { "name": "dockview-angular", - "version": "2.0.0", + "version": "2.1.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^2.0.0" + "dockview-core": "^2.1.0" } } diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index 01e63a421..8a1c3013b 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "2.0.0", + "version": "2.1.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", diff --git a/packages/dockview-react/package.json b/packages/dockview-react/package.json index c0051e41f..15566d1c5 100644 --- a/packages/dockview-react/package.json +++ b/packages/dockview-react/package.json @@ -1,6 +1,6 @@ { "name": "dockview-react", - "version": "2.0.0", + "version": "2.1.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage" }, "dependencies": { - "dockview": "^2.0.0" + "dockview": "^2.1.0" } } diff --git a/packages/dockview-vue/package.json b/packages/dockview-vue/package.json index d5f23fd8a..39ee42a1c 100644 --- a/packages/dockview-vue/package.json +++ b/packages/dockview-vue/package.json @@ -1,6 +1,6 @@ { "name": "dockview-vue", - "version": "2.0.0", + "version": "2.1.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -52,6 +52,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage" }, "dependencies": { - "dockview-core": "^2.0.0" + "dockview-core": "^2.1.0" } } diff --git a/packages/dockview/package.json b/packages/dockview/package.json index a26e233c4..717876e96 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "2.0.0", + "version": "2.1.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,7 +54,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^2.0.0" + "dockview-core": "^2.1.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/packages/docs/package.json b/packages/docs/package.json index 25f23a5df..cbc0e4c21 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "2.0.0", + "version": "2.1.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": "^2.0.0", + "dockview": "^2.1.0", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", "react-laag": "^2.0.5", From 684b0593e7a7c8591d7b8334b5fa5b947e3aebe7 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Thu, 19 Dec 2024 21:33:55 +0000 Subject: [PATCH 04/83] chore: update auto-gen docs --- packages/docs/src/generated/api.output.json | 854 +++++++++++++------- 1 file changed, 578 insertions(+), 276 deletions(-) diff --git a/packages/docs/src/generated/api.output.json b/packages/docs/src/generated/api.output.json index d7024baab..2e827390f 100644 --- a/packages/docs/src/generated/api.output.json +++ b/packages/docs/src/generated/api.output.json @@ -315,6 +315,34 @@ "isReadonly": true } }, + { + "name": "onDidMaximizedChange", + "code": "Event>", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "MaximizedChanged", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "T", + "source": "dockview-core", + "refersToTypeParameter": true + } + ] + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "onDidRemove", "code": "Event", @@ -540,27 +568,6 @@ } } }, - { - "name": "onDidMaximizedGroupChange", - "code": "Event", - "kind": "accessor", - "value": { - "name": "onDidMaximizedGroupChange", - "code": "Event", - "kind": "getSignature", - "returnType": { - "type": "reference", - "value": "Event", - "source": "dockview-core", - "typeArguments": [ - { - "type": "intrinsic", - "value": "void" - } - ] - } - } - }, { "name": "size", "code": "number", @@ -2352,11 +2359,11 @@ }, { "name": "onDidMaximizedGroupChange", - "code": "Event", + "code": "Event", "kind": "accessor", "value": { "name": "onDidMaximizedGroupChange", - "code": "Event", + "code": "Event", "kind": "getSignature", "returnType": { "type": "reference", @@ -2364,8 +2371,9 @@ "source": "dockview-core", "typeArguments": [ { - "type": "intrinsic", - "value": "void" + "type": "reference", + "value": "DockviewMaximizedGroupChanged", + "source": "dockview-core" } ] } @@ -4356,6 +4364,53 @@ "isReadonly": true } }, + { + "name": "onDidMaximizedChange", + "code": "Event>", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "MaximizedChanged", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "DockviewGroupPanel", + "source": "dockview-core" + } + ] + } + ] + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "onDidMaximizedGroupChange", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "DockviewMaximizedGroupChanged", + "source": "dockview-core" + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "onDidMovePanel", "code": "Event", @@ -4824,27 +4879,6 @@ } } }, - { - "name": "onDidMaximizedGroupChange", - "code": "Event", - "kind": "accessor", - "value": { - "name": "onDidMaximizedGroupChange", - "code": "Event", - "kind": "getSignature", - "returnType": { - "type": "reference", - "value": "Event", - "source": "dockview-core", - "typeArguments": [ - { - "type": "intrinsic", - "value": "void" - } - ] - } - } - }, { "name": "options", "code": "DockviewComponentOptions", @@ -5155,7 +5189,7 @@ }, { "name": "addPopoutGroup", - "code": "(itemToPopout: DockviewPanel | DockviewGroupPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, overridePopoutGroup?: DockviewGroupPanel, popoutUrl?: string, position?: Box }): Promise", + "code": "(itemToPopout: DockviewPanel | DockviewGroupPanel, options?: DockviewPopoutGroupOptions): Promise", "kind": "method", "signature": [ { @@ -5184,186 +5218,11 @@ }, { "name": "options", - "code": "options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, overridePopoutGroup?: DockviewGroupPanel, popoutUrl?: string, position?: Box }", + "code": "options?: DockviewPopoutGroupOptions", "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "{ onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, overridePopoutGroup?: DockviewGroupPanel, popoutUrl?: string, position?: Box }", - "kind": "typeLiteral", - "properties": [ - { - "name": "onDidOpen", - "code": "(event: { id: string, window: Window }): void", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "(event: { id: string, window: Window }): void", - "kind": "typeLiteral", - "signatures": [ - { - "name": "__type", - "typeParameters": [], - "parameters": [ - { - "name": "event", - "code": "event: { id: string, window: Window }", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "{ id: string, window: Window }", - "kind": "typeLiteral", - "properties": [ - { - "name": "id", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": {} - }, - { - "name": "window", - "code": "Window", - "kind": "property", - "type": { - "type": "reference", - "value": "Window", - "source": "typescript" - }, - "flags": {} - } - ] - } - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "void" - }, - "code": "(event: { id: string, window: Window }): void", - "kind": "callSignature" - } - ] - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "onWillClose", - "code": "(event: { id: string, window: Window }): void", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "(event: { id: string, window: Window }): void", - "kind": "typeLiteral", - "signatures": [ - { - "name": "__type", - "typeParameters": [], - "parameters": [ - { - "name": "event", - "code": "event: { id: string, window: Window }", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "{ id: string, window: Window }", - "kind": "typeLiteral", - "properties": [ - { - "name": "id", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": {} - }, - { - "name": "window", - "code": "Window", - "kind": "property", - "type": { - "type": "reference", - "value": "Window", - "source": "typescript" - }, - "flags": {} - } - ] - } - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "void" - }, - "code": "(event: { id: string, window: Window }): void", - "kind": "callSignature" - } - ] - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "overridePopoutGroup", - "code": "DockviewGroupPanel", - "kind": "property", - "type": { - "type": "reference", - "value": "DockviewGroupPanel", - "source": "dockview-core" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "popoutUrl", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "position", - "code": "Box", - "kind": "property", - "type": { - "type": "reference", - "value": "Box", - "source": "dockview-core" - }, - "flags": { - "isOptional": true - } - } - ] - } + "type": "reference", + "value": "DockviewPopoutGroupOptions", + "source": "dockview-core" }, "kind": "parameter" } @@ -5379,7 +5238,7 @@ } ] }, - "code": "(itemToPopout: DockviewPanel | DockviewGroupPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, overridePopoutGroup?: DockviewGroupPanel, popoutUrl?: string, position?: Box }): Promise", + "code": "(itemToPopout: DockviewPanel | DockviewGroupPanel, options?: DockviewPopoutGroupOptions): Promise", "kind": "callSignature" } ] @@ -6261,7 +6120,7 @@ }, { "name": "removePanel", - "code": "(panel: IDockviewPanel, options: { removeEmptyGroup: boolean, skipDispose: boolean, skipSetActiveGroup?: boolean }): void", + "code": "(panel: IDockviewPanel, options: { removeEmptyGroup: boolean, skipDispose?: boolean, skipSetActiveGroup?: boolean }): void", "kind": "method", "signature": [ { @@ -6280,12 +6139,12 @@ }, { "name": "options", - "code": "options: { removeEmptyGroup: boolean, skipDispose: boolean, skipSetActiveGroup?: boolean }", + "code": "options: { removeEmptyGroup: boolean, skipDispose?: boolean, skipSetActiveGroup?: boolean }", "type": { "type": "reflection", "value": { "name": "__type", - "code": "{ removeEmptyGroup: boolean, skipDispose: boolean, skipSetActiveGroup?: boolean }", + "code": "{ removeEmptyGroup: boolean, skipDispose?: boolean, skipSetActiveGroup?: boolean }", "kind": "typeLiteral", "properties": [ { @@ -6306,7 +6165,9 @@ "type": "intrinsic", "value": "boolean" }, - "flags": {} + "flags": { + "isOptional": true + } }, { "name": "skipSetActiveGroup", @@ -6330,7 +6191,7 @@ "type": "intrinsic", "value": "void" }, - "code": "(panel: IDockviewPanel, options: { removeEmptyGroup: boolean, skipDispose: boolean, skipSetActiveGroup?: boolean }): void", + "code": "(panel: IDockviewPanel, options: { removeEmptyGroup: boolean, skipDispose?: boolean, skipSetActiveGroup?: boolean }): void", "kind": "callSignature" } ] @@ -10682,7 +10543,7 @@ }, { "name": "onDidMaximizedNodeChange", - "code": "Event", + "code": "Event", "kind": "property", "type": { "type": "reference", @@ -10690,8 +10551,9 @@ "source": "dockview-core", "typeArguments": [ { - "type": "intrinsic", - "value": "void" + "type": "reference", + "value": "MaximizedViewChanged", + "source": "dockview-core" } ] }, @@ -13011,6 +12873,40 @@ "isReadonly": true } }, + { + "name": "onDidMaximizedChange", + "code": "Event>>", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "MaximizedChanged", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "GridviewPanel", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "GridviewPanelApiImpl", + "source": "dockview-core" + } + ] + } + ] + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "onDidRemove", "code": "Event>", @@ -13293,27 +13189,6 @@ } } }, - { - "name": "onDidMaximizedGroupChange", - "code": "Event", - "kind": "accessor", - "value": { - "name": "onDidMaximizedGroupChange", - "code": "Event", - "kind": "getSignature", - "returnType": { - "type": "reference", - "value": "Event", - "source": "dockview-core", - "typeArguments": [ - { - "type": "intrinsic", - "value": "void" - } - ] - } - } - }, { "name": "options", "code": "GridviewComponentOptions", @@ -24313,6 +24188,34 @@ ], "extends": [] }, + "DockviewMaximizedGroupChanged": { + "kind": "interface", + "name": "DockviewMaximizedGroupChanged", + "children": [ + { + "name": "group", + "code": "DockviewGroupPanel", + "kind": "property", + "type": { + "type": "reference", + "value": "DockviewGroupPanel", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "isMaximized", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": {} + } + ], + "extends": [] + }, "DockviewOptions": { "kind": "interface", "name": "DockviewOptions", @@ -24517,6 +24420,43 @@ "isOptional": true } }, + { + "name": "noPanelsOverlay", + "code": "'watermark' | 'emptyGroup'", + "kind": "property", + "type": { + "type": "or", + "values": [ + { + "type": "literal", + "value": "watermark" + }, + { + "type": "literal", + "value": "emptyGroup" + } + ] + }, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Define the behaviour of the dock when there are no panels to display. Defaults to " + }, + { + "kind": "code", + "text": "`watermark`" + }, + { + "kind": "text", + "text": "." + } + ] + } + }, { "name": "popoutUrl", "code": "string", @@ -25333,6 +25273,207 @@ ], "extends": [] }, + "DockviewPopoutGroupOptions": { + "kind": "interface", + "name": "DockviewPopoutGroupOptions", + "children": [ + { + "name": "onDidOpen", + "code": "(event: { id: string, window: Window }): void", + "kind": "property", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "(event: { id: string, window: Window }): void", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [ + { + "name": "event", + "code": "event: { id: string, window: Window }", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "{ id: string, window: Window }", + "kind": "typeLiteral", + "properties": [ + { + "name": "id", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": {} + }, + { + "name": "window", + "code": "Window", + "kind": "property", + "type": { + "type": "reference", + "value": "Window", + "source": "typescript" + }, + "flags": {} + } + ] + } + }, + "kind": "parameter" + } + ], + "returnType": { + "type": "intrinsic", + "value": "void" + }, + "code": "(event: { id: string, window: Window }): void", + "kind": "callSignature" + } + ] + } + }, + "flags": { + "isOptional": true + } + }, + { + "name": "onWillClose", + "code": "(event: { id: string, window: Window }): void", + "kind": "property", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "(event: { id: string, window: Window }): void", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [ + { + "name": "event", + "code": "event: { id: string, window: Window }", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "{ id: string, window: Window }", + "kind": "typeLiteral", + "properties": [ + { + "name": "id", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": {} + }, + { + "name": "window", + "code": "Window", + "kind": "property", + "type": { + "type": "reference", + "value": "Window", + "source": "typescript" + }, + "flags": {} + } + ] + } + }, + "kind": "parameter" + } + ], + "returnType": { + "type": "intrinsic", + "value": "void" + }, + "code": "(event: { id: string, window: Window }): void", + "kind": "callSignature" + } + ] + } + }, + "flags": { + "isOptional": true + } + }, + { + "name": "overridePopoutGroup", + "code": "DockviewGroupPanel", + "kind": "property", + "type": { + "type": "reference", + "value": "DockviewGroupPanel", + "source": "dockview-core" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "popoutUrl", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The same-origin path at which the popout window will be created\n\nDefaults to " + }, + { + "kind": "code", + "text": "`/popout.html`" + }, + { + "kind": "text", + "text": " if not provided" + } + ] + } + }, + { + "name": "position", + "code": "Box", + "kind": "property", + "type": { + "type": "reference", + "value": "Box", + "source": "dockview-core" + }, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The position of the popout group" + } + ] + } + } + ], + "extends": [] + }, "DockviewReadyEvent": { "kind": "interface", "name": "DockviewReadyEvent", @@ -27325,8 +27466,8 @@ } }, { - "name": "onDidMaximizedGroupChange", - "code": "Event", + "name": "onDidMaximizedChange", + "code": "Event>", "kind": "property", "type": { "type": "reference", @@ -27334,8 +27475,17 @@ "source": "dockview-core", "typeArguments": [ { - "type": "intrinsic", - "value": "void" + "type": "reference", + "value": "MaximizedChanged", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "T", + "source": "dockview-core", + "refersToTypeParameter": true + } + ] } ] }, @@ -28658,8 +28808,8 @@ } }, { - "name": "onDidMaximizedGroupChange", - "code": "Event", + "name": "onDidMaximizedChange", + "code": "Event>", "kind": "property", "type": { "type": "reference", @@ -28667,8 +28817,36 @@ "source": "dockview-core", "typeArguments": [ { - "type": "intrinsic", - "value": "void" + "type": "reference", + "value": "MaximizedChanged", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "DockviewGroupPanel", + "source": "dockview-core" + } + ] + } + ] + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "onDidMaximizedGroupChange", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "DockviewMaximizedGroupChanged", + "source": "dockview-core" } ] }, @@ -33038,8 +33216,8 @@ } }, { - "name": "onDidMaximizedGroupChange", - "code": "Event", + "name": "onDidMaximizedChange", + "code": "Event>>", "kind": "property", "type": { "type": "reference", @@ -33047,8 +33225,23 @@ "source": "dockview-core", "typeArguments": [ { - "type": "intrinsic", - "value": "void" + "type": "reference", + "value": "MaximizedChanged", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "GridviewPanel", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "GridviewPanelApiImpl", + "source": "dockview-core" + } + ] + } + ] } ] }, @@ -37421,6 +37614,63 @@ "Optional" ] }, + "MaximizedChanged": { + "kind": "interface", + "name": "MaximizedChanged", + "children": [ + { + "name": "isMaximized", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": {} + }, + { + "name": "panel", + "code": "MaximizedChanged.T", + "kind": "property", + "type": { + "type": "reference", + "value": "T", + "source": "dockview-core", + "refersToTypeParameter": true + }, + "flags": {} + } + ], + "extends": [] + }, + "MaximizedViewChanged": { + "kind": "interface", + "name": "MaximizedViewChanged", + "children": [ + { + "name": "isMaximized", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": {} + }, + { + "name": "view", + "code": "IGridView", + "kind": "property", + "type": { + "type": "reference", + "value": "IGridView", + "source": "dockview-core" + }, + "flags": {} + } + ], + "extends": [] + }, "MovePanelEvent": { "kind": "interface", "name": "MovePanelEvent", @@ -39720,6 +39970,19 @@ }, "flags": {} }, + { + "name": "maximizedNode", + "code": "SerializedNodeDescriptor", + "kind": "property", + "type": { + "type": "reference", + "value": "SerializedNodeDescriptor", + "source": "dockview-core" + }, + "flags": { + "isOptional": true + } + }, { "name": "orientation", "code": "Orientation", @@ -39800,6 +40063,26 @@ ], "extends": [] }, + "SerializedNodeDescriptor": { + "kind": "interface", + "name": "SerializedNodeDescriptor", + "children": [ + { + "name": "location", + "code": "number[]", + "kind": "property", + "type": { + "type": "array", + "value": { + "type": "intrinsic", + "value": "number" + } + }, + "flags": {} + } + ], + "extends": [] + }, "SerializedPaneview": { "kind": "interface", "name": "SerializedPaneview", @@ -40044,6 +40327,18 @@ ] }, "flags": {} + }, + { + "name": "url", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": { + "isOptional": true + } } ], "extends": [] @@ -41496,7 +41791,7 @@ }, "DockviewGroupLocation": { "name": "DockviewGroupLocation", - "code": "{ getWindow: (): Window, type: 'popout' } | { type: 'floating' } | { type: 'grid' }", + "code": "{ getWindow: (): Window, popoutUrl?: string, type: 'popout' } | { type: 'floating' } | { type: 'grid' }", "typeParameters": [], "type": { "type": "or", @@ -41505,7 +41800,7 @@ "type": "reflection", "value": { "name": "__type", - "code": "{ getWindow: (): Window, type: 'popout' }", + "code": "{ getWindow: (): Window, popoutUrl?: string, type: 'popout' }", "kind": "typeLiteral", "properties": [ { @@ -41536,6 +41831,18 @@ }, "flags": {} }, + { + "name": "popoutUrl", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": { + "isOptional": true + } + }, { "name": "type", "code": "'popout'", @@ -43852,11 +44159,6 @@ }, "kind": "typeAlias" }, - "ReactPartContext": { - "name": "ReactPartContext", - "code": "", - "kind": "variable" - }, "DockviewDefaultTab": { "kind": "function", "name": "DockviewDefaultTab", @@ -44410,7 +44712,7 @@ "summary": [ { "kind": "text", - "text": "A React Hook that returns an array of portals to be rendered by the user of this hook\r\nand a disposable function to add a portal. Calling dispose removes this portal from the\r\nportal array" + "text": "A React Hook that returns an array of portals to be rendered by the user of this hook\nand a disposable function to add a portal. Calling dispose removes this portal from the\nportal array" } ] }, From dd918294d1c893d3550c2bbf3e05a0cdeff535fb Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Thu, 19 Dec 2024 22:05:59 +0000 Subject: [PATCH 05/83] bug: only dispose of ref group if empty --- .../dockview/dockviewComponent.spec.ts | 45 +++++++++++++++++++ .../src/dockview/dockviewComponent.ts | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index cee519dbb..c646ca0a5 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -4867,6 +4867,51 @@ describe('dockviewComponent', () => { ); }); + test('move popout group of 1 panel inside grid', async () => { + const container = document.createElement('div'); + + window.open = () => setupMockWindow(); + + const dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + dockview.layout(1000, 500); + + const panel1 = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + + const panel2 = dockview.addPanel({ + id: 'panel_2', + component: 'default', + }); + + const panel3 = dockview.addPanel({ + id: 'panel_3', + component: 'default', + position: { direction: 'right' }, + }); + + await dockview.addPopoutGroup(panel2); + + panel2.api.moveTo({ position: 'top', group: panel3.group }); + + expect(dockview.panels.length).toBe(3); + expect(dockview.groups.length).toBe(3); + }); + test('add a popout group', async () => { const container = document.createElement('div'); diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index c7224d687..cf7823d9f 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -1885,7 +1885,7 @@ export class DockviewComponent const refGroup = selectedGroup.referenceGroup ? this.getPanel(selectedGroup.referenceGroup) : undefined; - if (refGroup) { + if (refGroup && refGroup.panels.length === 0) { this.removeGroup(refGroup); } } From 140a61f401c06a4113143432511ec1b71033a474 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Fri, 20 Dec 2024 20:06:49 +0000 Subject: [PATCH 06/83] chore: v2.1.1 docs --- .../docs/blog/2024-12-20-dockview-2.1.1.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 packages/docs/blog/2024-12-20-dockview-2.1.1.md diff --git a/packages/docs/blog/2024-12-20-dockview-2.1.1.md b/packages/docs/blog/2024-12-20-dockview-2.1.1.md new file mode 100644 index 000000000..106fb8105 --- /dev/null +++ b/packages/docs/blog/2024-12-20-dockview-2.1.1.md @@ -0,0 +1,19 @@ +--- +slug: dockview-2.1.0-release +title: Dockview 2.1.0 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +- Bug: Fix issue with moving tab from popout group into main grid [#795](https://github.com/mathuo/dockview/issues/795) + +## 🔥 Breaking changes + + From 53cdf42d84ddb51a6a93e95f8aac9feeba6caa45 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Fri, 20 Dec 2024 20:07:17 +0000 Subject: [PATCH 07/83] chore(release): publish v2.1.1 --- lerna.json | 2 +- packages/dockview-angular/package.json | 4 ++-- packages/dockview-core/package.json | 2 +- packages/dockview-react/package.json | 4 ++-- packages/dockview-vue/package.json | 4 ++-- packages/dockview/package.json | 4 ++-- packages/docs/package.json | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lerna.json b/lerna.json index 828231e80..022c18e44 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "2.1.0", + "version": "2.1.1", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-angular/package.json b/packages/dockview-angular/package.json index 7e7a7e54a..12a67fe40 100644 --- a/packages/dockview-angular/package.json +++ b/packages/dockview-angular/package.json @@ -1,6 +1,6 @@ { "name": "dockview-angular", - "version": "2.1.0", + "version": "2.1.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^2.1.0" + "dockview-core": "^2.1.1" } } diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index 8a1c3013b..7b4e83ffa 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "2.1.0", + "version": "2.1.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", diff --git a/packages/dockview-react/package.json b/packages/dockview-react/package.json index 15566d1c5..5c7b41ca4 100644 --- a/packages/dockview-react/package.json +++ b/packages/dockview-react/package.json @@ -1,6 +1,6 @@ { "name": "dockview-react", - "version": "2.1.0", + "version": "2.1.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage" }, "dependencies": { - "dockview": "^2.1.0" + "dockview": "^2.1.1" } } diff --git a/packages/dockview-vue/package.json b/packages/dockview-vue/package.json index 39ee42a1c..ca3e602fc 100644 --- a/packages/dockview-vue/package.json +++ b/packages/dockview-vue/package.json @@ -1,6 +1,6 @@ { "name": "dockview-vue", - "version": "2.1.0", + "version": "2.1.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -52,6 +52,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage" }, "dependencies": { - "dockview-core": "^2.1.0" + "dockview-core": "^2.1.1" } } diff --git a/packages/dockview/package.json b/packages/dockview/package.json index 717876e96..6a560de1c 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "2.1.0", + "version": "2.1.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,7 +54,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^2.1.0" + "dockview-core": "^2.1.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/packages/docs/package.json b/packages/docs/package.json index cbc0e4c21..7f5e08f24 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "2.1.0", + "version": "2.1.1", "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": "^2.1.0", + "dockview": "^2.1.1", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", "react-laag": "^2.0.5", From 703418e27fb82174e42bdf3f2e029dec2bb4897a Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Fri, 20 Dec 2024 20:07:58 +0000 Subject: [PATCH 08/83] chore: typo --- packages/docs/blog/2024-12-20-dockview-2.1.1.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/docs/blog/2024-12-20-dockview-2.1.1.md b/packages/docs/blog/2024-12-20-dockview-2.1.1.md index 106fb8105..7594a64ee 100644 --- a/packages/docs/blog/2024-12-20-dockview-2.1.1.md +++ b/packages/docs/blog/2024-12-20-dockview-2.1.1.md @@ -1,6 +1,6 @@ --- -slug: dockview-2.1.0-release -title: Dockview 2.1.0 +slug: dockview-2.1.1-release +title: Dockview 2.1.1 tags: [release] --- From 7515b9b05de00477492c6bd21bb7e40aea177a8c Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 21 Dec 2024 13:07:18 +0000 Subject: [PATCH 09/83] bug: fixup popout group flows --- .../dockview/dockviewComponent.spec.ts | 152 +++++++++++++++++- .../src/dockview/dockviewComponent.ts | 57 +++++-- .../dockview-core/src/dockview/options.ts | 1 + 3 files changed, 198 insertions(+), 12 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index c646ca0a5..c6dc7a038 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -21,6 +21,7 @@ import { DockviewApi } from '../../api/component.api'; import { DockviewDndOverlayEvent } from '../../dockview/options'; import { SizeEvent } from '../../api/gridviewPanelApi'; import { setupMockWindow } from '../__mocks__/mockWindow'; +import { exhaustMicrotaskQueue } from '../__test_utils__/utils'; class PanelContentPartTest implements IContentRenderer { element: HTMLElement = document.createElement('div'); @@ -4867,6 +4868,155 @@ describe('dockviewComponent', () => { ); }); + test('basic', async () => { + jest.useRealTimers(); + + const container = document.createElement('div'); + + window.open = () => setupMockWindow(); + + const dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + dockview.layout(1000, 1000); + + dockview.fromJSON({ + activeGroup: 'group-1', + grid: { + root: { + type: 'branch', + data: [ + { + type: 'leaf', + data: { + views: ['panel1'], + id: 'group-1', + activeView: 'panel1', + }, + size: 1000, + }, + ], + size: 1000, + }, + height: 1000, + width: 1000, + orientation: Orientation.VERTICAL, + }, + popoutGroups: [ + { + data: { + views: ['panel2'], + id: 'group-2', + activeView: 'panel2', + }, + position: null, + }, + ], + panels: { + panel1: { + id: 'panel1', + contentComponent: 'default', + title: 'panel1', + }, + panel2: { + id: 'panel2', + contentComponent: 'default', + title: 'panel2', + }, + }, + }); + + await new Promise((resolve) => setTimeout(resolve, 0)); + + const panel2 = dockview.api.getPanel('panel2'); + + const windowObject = + panel2?.api.location.type === 'popout' + ? panel2?.api.location.getWindow() + : undefined; + + expect(windowObject).toBeTruthy(); + + windowObject!.close(); + }); + + test('grid -> floating -> popout -> popout closed', async () => { + const container = document.createElement('div'); + + window.open = () => setupMockWindow(); + + const dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + dockview.layout(1000, 500); + + const panel1 = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + + const panel2 = dockview.addPanel({ + id: 'panel_2', + component: 'default', + }); + + const panel3 = dockview.addPanel({ + id: 'panel_3', + component: 'default', + position: { direction: 'right' }, + }); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('grid'); + expect(panel3.api.location.type).toBe('grid'); + + dockview.addFloatingGroup(panel2); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('floating'); + expect(panel3.api.location.type).toBe('grid'); + + await dockview.addPopoutGroup(panel2); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('popout'); + expect(panel3.api.location.type).toBe('grid'); + + const windowObject = + panel2.api.location.type === 'popout' + ? panel2.api.location.getWindow() + : undefined; + expect(windowObject).toBeTruthy(); + + windowObject!.close(); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('floating'); + expect(panel3.api.location.type).toBe('grid'); + }); + test('move popout group of 1 panel inside grid', async () => { const container = document.createElement('div'); @@ -5116,7 +5266,7 @@ describe('dockviewComponent', () => { mockWindow.close(); expect(panel1.group.api.location.type).toBe('grid'); - expect(panel2.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(panel3.group.api.location.type).toBe('grid'); dockview.clear(); diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index cf7823d9f..9d49df053 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -706,6 +706,8 @@ export class DockviewComponent _window.window!.innerHeight ); + let floatingBox: AnchoredBox | undefined; + if (!options?.overridePopoutGroup && isGroupAddedToDom) { if (itemToPopout instanceof DockviewPanel) { this.movingLock(() => { @@ -727,7 +729,15 @@ export class DockviewComponent break; case 'floating': case 'popout': + floatingBox = this._floatingGroups + .find( + (value) => + value.group.api.id === + itemToPopout.api.id + ) + ?.overlay.toJSON(); this.removeGroup(referenceGroup); + break; } } @@ -825,20 +835,29 @@ export class DockviewComponent }); } } else if (this.getPanel(group.id)) { - this.doRemoveGroup(group, { - skipDispose: true, - skipActive: true, - skipPopoutReturn: true, - }); - const removedGroup = group; - removedGroup.model.renderContainer = - this.overlayRenderContainer; - removedGroup.model.location = { type: 'grid' }; - returnedGroup = removedGroup; + if (floatingBox) { + this.addFloatingGroup(removedGroup, { + height: floatingBox.height, + width: floatingBox.width, + position: floatingBox, + }); + } else { + this.doRemoveGroup(removedGroup, { + skipDispose: true, + skipActive: true, + skipPopoutReturn: true, + }); - this.doAddGroup(removedGroup, [0]); + removedGroup.model.renderContainer = + this.overlayRenderContainer; + removedGroup.model.location = { type: 'grid' }; + returnedGroup = removedGroup; + this.movingLock(() => { + this.doAddGroup(removedGroup, [0]); + }); + } this.doSetGroupAndPanelActive(removedGroup); } }) @@ -1477,6 +1496,22 @@ export class DockviewComponent ); } + if (options.popout) { + const group = this.createGroup(); + this._onDidAddGroup.fire(group); + + this.addPopoutGroup(group); + + const panel = this.createPanel(options, group); + + group.model.openPanel(panel, { + skipSetActive: options.inactive, + skipSetGroupActive: options.inactive, + }); + + return panel; + } + const initial = { width: options.initialWidth, height: options.initialHeight, diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 3e87923a2..d21418730 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -252,6 +252,7 @@ export type AddPanelOptions

= { inactive?: boolean; initialWidth?: number; initialHeight?: number; + popout?: boolean; } & Partial & Partial; From 5d868e63ceaf9e1e4051bfd5ddc6cd11340b9534 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 21 Dec 2024 13:58:11 +0000 Subject: [PATCH 10/83] feat: additional event sequencing checks --- .../dockview/dockviewComponent.spec.ts | 8 +-- .../src/dockview/dockviewComponent.ts | 9 +++- .../src/dockview/strictEventsSequencing.ts | 54 +++++++++++++++++++ 3 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 packages/dockview-core/src/dockview/strictEventsSequencing.ts diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index c6dc7a038..fd5981b78 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -3708,16 +3708,16 @@ describe('dockviewComponent', () => { floatingGroups: [ { data: { - views: ['panelB'], - activeView: 'panelB', + views: ['panelC'], + activeView: 'panelC', id: '3', }, position: { left: 0, top: 0, height: 100, width: 100 }, }, { data: { - views: ['panelC'], - activeView: 'panelC', + views: ['panelD'], + activeView: 'panelD', id: '4', }, position: { left: 0, top: 0, height: 100, width: 100 }, diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 9d49df053..152285f39 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -73,6 +73,7 @@ import { OverlayRenderContainer, } from '../overlay/overlayRenderContainer'; import { PopoutWindow } from '../popoutWindow'; +import { StrictEventsSequencing } from './strictEventsSequencing'; const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = { activationSize: { type: 'pixels', value: 10 }, @@ -388,6 +389,10 @@ export class DockviewComponent toggleClass(this.gridview.element, 'dv-dockview', true); toggleClass(this.element, 'dv-debug', !!options.debug); + if (options.debug) { + this.addDisposables(new StrictEventsSequencing(this)); + } + this.addDisposables( this.overlayRenderContainer, this._onWillDragPanel, @@ -1309,6 +1314,7 @@ export class DockviewComponent locked: !!locked, hideHeader: !!hideHeader, }); + this._onDidAddGroup.fire(group); const createdPanels: IDockviewPanel[] = []; @@ -1325,8 +1331,6 @@ export class DockviewComponent createdPanels.push(panel); } - this._onDidAddGroup.fire(group); - for (let i = 0; i < views.length; i++) { const panel = createdPanels[i]; @@ -1413,6 +1417,7 @@ export class DockviewComponent 'dockview: failed to deserialize layout. Reverting changes', err ); + /** * Takes all the successfully created groups and remove all of their panels. */ diff --git a/packages/dockview-core/src/dockview/strictEventsSequencing.ts b/packages/dockview-core/src/dockview/strictEventsSequencing.ts new file mode 100644 index 000000000..60f91e613 --- /dev/null +++ b/packages/dockview-core/src/dockview/strictEventsSequencing.ts @@ -0,0 +1,54 @@ +import { CompositeDisposable } from '../lifecycle'; +import { DockviewComponent } from './dockviewComponent'; + +export class StrictEventsSequencing extends CompositeDisposable { + constructor(private readonly accessor: DockviewComponent) { + super(); + + this.init(); + } + + private init(): void { + const panels = new Set(); + const groups = new Set(); + + this.addDisposables( + this.accessor.onDidAddPanel((panel) => { + if (panels.has(panel.api.id)) { + throw new Error( + `dockview: Invalid event sequence. [onDidAddPanel] called for panel ${panel.api.id} but panel already exists` + ); + } else { + panels.add(panel.api.id); + } + }), + this.accessor.onDidRemovePanel((panel) => { + if (!panels.has(panel.api.id)) { + throw new Error( + `dockview: Invalid event sequence. [onDidRemovePanel] called for panel ${panel.api.id} but panel does not exists` + ); + } else { + panels.delete(panel.api.id); + } + }), + this.accessor.onDidAddGroup((group) => { + if (groups.has(group.api.id)) { + throw new Error( + `dockview: Invalid event sequence. [onDidAddGroup] called for group ${group.api.id} but group already exists` + ); + } else { + groups.add(group.api.id); + } + }), + this.accessor.onDidRemoveGroup((group) => { + if (!groups.has(group.api.id)) { + throw new Error( + `dockview: Invalid event sequence. [onDidRemoveGroup] called for group ${group.api.id} but group does not exists` + ); + } else { + groups.delete(group.api.id); + } + }) + ); + } +} From 0d32d285d2063d2b655d6bf46a5beb484175d9cd Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 21 Dec 2024 13:59:24 +0000 Subject: [PATCH 11/83] chore: remove testing code --- .../src/dockview/dockviewComponent.ts | 16 ---------------- packages/dockview-core/src/dockview/options.ts | 1 - 2 files changed, 17 deletions(-) diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 152285f39..547c47120 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -1501,22 +1501,6 @@ export class DockviewComponent ); } - if (options.popout) { - const group = this.createGroup(); - this._onDidAddGroup.fire(group); - - this.addPopoutGroup(group); - - const panel = this.createPanel(options, group); - - group.model.openPanel(panel, { - skipSetActive: options.inactive, - skipSetGroupActive: options.inactive, - }); - - return panel; - } - const initial = { width: options.initialWidth, height: options.initialHeight, diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index d21418730..3e87923a2 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -252,7 +252,6 @@ export type AddPanelOptions

= { inactive?: boolean; initialWidth?: number; initialHeight?: number; - popout?: boolean; } & Partial & Partial; From c216d703544dcd3686164dd3c4abefe8ef4abfbe Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 21 Dec 2024 14:00:57 +0000 Subject: [PATCH 12/83] chore: comments --- packages/dockview-core/src/dockview/dockviewComponent.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 547c47120..42cbe01c7 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -741,6 +741,7 @@ export class DockviewComponent itemToPopout.api.id ) ?.overlay.toJSON(); + this.removeGroup(referenceGroup); break; @@ -859,7 +860,9 @@ export class DockviewComponent this.overlayRenderContainer; removedGroup.model.location = { type: 'grid' }; returnedGroup = removedGroup; + this.movingLock(() => { + // suppress group add events since the group already exists this.doAddGroup(removedGroup, [0]); }); } From f749372eb2246a793717b5011fc0a2605ce3a072 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 21 Dec 2024 14:21:28 +0000 Subject: [PATCH 13/83] chore: memory leak fixes and tests --- .../dockview/dockviewComponent.spec.ts | 176 +++++++++--------- .../src/dockview/dockviewComponent.ts | 6 + .../src/dockview/dockviewGroupPanelModel.ts | 4 +- .../src/gridview/baseComponentGridview.ts | 2 + 4 files changed, 101 insertions(+), 87 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index fd5981b78..e1ea35cbd 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -21,7 +21,6 @@ import { DockviewApi } from '../../api/component.api'; import { DockviewDndOverlayEvent } from '../../dockview/options'; import { SizeEvent } from '../../api/gridviewPanelApi'; import { setupMockWindow } from '../__mocks__/mockWindow'; -import { exhaustMicrotaskQueue } from '../__test_utils__/utils'; class PanelContentPartTest implements IContentRenderer { element: HTMLElement = document.createElement('div'); @@ -141,109 +140,114 @@ describe('dockviewComponent', () => { expect(dockview.element.className).toBe('test-b test-c'); }); - // describe('memory leakage', () => { - // beforeEach(() => { - // window.open = () => fromPartial({ - // addEventListener: jest.fn(), - // close: jest.fn(), - // }); - // }); + describe('memory leakage', () => { + beforeEach(() => { + window.open = () => setupMockWindow(); + }); - // test('event leakage', () => { - // Emitter.setLeakageMonitorEnabled(true); + test('event leakage', async () => { + Emitter.setLeakageMonitorEnabled(true); - // dockview = new DockviewComponent({ - // parentElement: container, - // components: { - // default: PanelContentPartTest, - // }, - // }); + dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + className: 'test-a test-b', + }); - // dockview.layout(500, 1000); + dockview.layout(500, 1000); - // const panel1 = dockview.addPanel({ - // id: 'panel1', - // component: 'default', - // }); + const panel1 = dockview.addPanel({ + id: 'panel1', + component: 'default', + }); - // const panel2 = dockview.addPanel({ - // id: 'panel2', - // component: 'default', - // }); + const panel2 = dockview.addPanel({ + id: 'panel2', + component: 'default', + }); - // dockview.removePanel(panel2); + dockview.removePanel(panel2); - // const panel3 = dockview.addPanel({ - // id: 'panel3', - // component: 'default', - // position: { - // direction: 'right', - // referencePanel: 'panel1', - // }, - // }); + const panel3 = dockview.addPanel({ + id: 'panel3', + component: 'default', + position: { + direction: 'right', + referencePanel: 'panel1', + }, + }); - // const panel4 = dockview.addPanel({ - // id: 'panel4', - // component: 'default', - // position: { - // direction: 'above', - // }, - // }); + const panel4 = dockview.addPanel({ + id: 'panel4', + component: 'default', + position: { + direction: 'above', + }, + }); - // dockview.moveGroupOrPanel( - // panel4.group, - // panel3.group.id, - // panel3.id, - // 'center' - // ); + panel4.api.group.api.moveTo({ + group: panel3.api.group, + position: 'center', + }); - // dockview.addPanel({ - // id: 'panel5', - // component: 'default', - // floating: true, - // }); + dockview.addPanel({ + id: 'panel5', + component: 'default', + floating: true, + }); - // const panel6 = dockview.addPanel({ - // id: 'panel6', - // component: 'default', - // position: { - // referencePanel: 'panel5', - // direction: 'within', - // }, - // }); + const panel6 = dockview.addPanel({ + id: 'panel6', + component: 'default', + position: { + referencePanel: 'panel5', + direction: 'within', + }, + }); - // dockview.addFloatingGroup(panel4.api.group); + dockview.addFloatingGroup(panel4.api.group); - // dockview.addPopoutGroup(panel6); + await dockview.addPopoutGroup(panel2); - // dockview.moveGroupOrPanel( - // panel1.group, - // panel6.group.id, - // panel6.id, - // 'center' - // ); + panel1.api.group.api.moveTo({ + group: panel6.api.group, + position: 'center', + }); - // dockview.moveGroupOrPanel( - // panel4.group, - // panel6.group.id, - // panel6.id, - // 'center' - // ); + panel4.api.group.api.moveTo({ + group: panel6.api.group, + position: 'center', + }); - // dockview.dispose(); + dockview.dispose(); - // if (Emitter.MEMORY_LEAK_WATCHER.size > 0) { - // for (const entry of Array.from( - // Emitter.MEMORY_LEAK_WATCHER.events - // )) { - // console.log('disposal', entry[1]); - // } - // throw new Error('not all listeners disposed'); - // } + if (Emitter.MEMORY_LEAK_WATCHER.size > 0) { + console.warn( + `${Emitter.MEMORY_LEAK_WATCHER.size} undisposed resources` + ); - // Emitter.setLeakageMonitorEnabled(false); - // }); - // }); + for (const entry of Array.from( + Emitter.MEMORY_LEAK_WATCHER.events + )) { + console.log('disposal', entry[1]); + } + throw new Error( + `${Emitter.MEMORY_LEAK_WATCHER.size} undisposed resources` + ); + } + + Emitter.setLeakageMonitorEnabled(false); + }); + }); test('duplicate panel', () => { dockview.layout(500, 1000); diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 42cbe01c7..d0d1c39aa 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -409,6 +409,7 @@ export class DockviewComponent this._onDidRemoveGroup, this._onDidActiveGroupChange, this._onUnhandledDragOverEvent, + this._onDidMaximizedGroupChange, this.onDidViewVisibilityChangeMicroTaskQueue(() => { this.updateWatermark(); }), @@ -576,6 +577,11 @@ export class DockviewComponent this.updateWatermark(); } + override dispose(): void { + this.clear(); // explicitly clear the layout before cleaning up + super.dispose(); + } + override setVisible(panel: DockviewGroupPanel, visible: boolean): void { switch (panel.api.location.type) { case 'grid': diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 17af6e573..a34d5ef10 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -506,7 +506,9 @@ export class DockviewGroupPanelModel this._onDidAddPanel, this._onDidRemovePanel, this._onDidActivePanelChange, - this._onUnhandledDragOverEvent + this._onUnhandledDragOverEvent, + this._onDidPanelTitleChange, + this._onDidPanelParametersChange ); } diff --git a/packages/dockview-core/src/gridview/baseComponentGridview.ts b/packages/dockview-core/src/gridview/baseComponentGridview.ts index 44e810a50..9d02993f3 100644 --- a/packages/dockview-core/src/gridview/baseComponentGridview.ts +++ b/packages/dockview-core/src/gridview/baseComponentGridview.ts @@ -205,6 +205,8 @@ export abstract class BaseGrid )(() => { this._bufferOnDidLayoutChange.fire(); }), + this._onDidMaximizedChange, + this._onDidViewVisibilityChangeMicroTaskQueue, this._bufferOnDidLayoutChange ); } From 56d4dc7c740835c0b2392fa9646d0c0165ebd174 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 21 Dec 2024 19:25:06 +0000 Subject: [PATCH 14/83] chore: v2.1.2 docs --- .../docs/blog/2024-12-21-dockview-2.1.2.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 packages/docs/blog/2024-12-21-dockview-2.1.2.md diff --git a/packages/docs/blog/2024-12-21-dockview-2.1.2.md b/packages/docs/blog/2024-12-21-dockview-2.1.2.md new file mode 100644 index 000000000..cc27b44b2 --- /dev/null +++ b/packages/docs/blog/2024-12-21-dockview-2.1.2.md @@ -0,0 +1,19 @@ +--- +slug: dockview-2.1.2-release +title: Dockview 2.1.2 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +- Bug: Fix issues with popout group location transitions [#797](https://github.com/mathuo/dockview/issues/797) + +## 🔥 Breaking changes + + From 7fe2e43d430d2715499c0978d432c6cb88f124f5 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 21 Dec 2024 19:25:31 +0000 Subject: [PATCH 15/83] chore(release): publish v2.1.2 --- lerna.json | 2 +- packages/dockview-angular/package.json | 4 ++-- packages/dockview-core/package.json | 2 +- packages/dockview-react/package.json | 4 ++-- packages/dockview-vue/package.json | 4 ++-- packages/dockview/package.json | 4 ++-- packages/docs/package.json | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lerna.json b/lerna.json index 022c18e44..401017841 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "2.1.1", + "version": "2.1.2", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-angular/package.json b/packages/dockview-angular/package.json index 12a67fe40..d25e4a128 100644 --- a/packages/dockview-angular/package.json +++ b/packages/dockview-angular/package.json @@ -1,6 +1,6 @@ { "name": "dockview-angular", - "version": "2.1.1", + "version": "2.1.2", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^2.1.1" + "dockview-core": "^2.1.2" } } diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index 7b4e83ffa..875cf6ea3 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "2.1.1", + "version": "2.1.2", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", diff --git a/packages/dockview-react/package.json b/packages/dockview-react/package.json index 5c7b41ca4..97ec1ab2e 100644 --- a/packages/dockview-react/package.json +++ b/packages/dockview-react/package.json @@ -1,6 +1,6 @@ { "name": "dockview-react", - "version": "2.1.1", + "version": "2.1.2", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage" }, "dependencies": { - "dockview": "^2.1.1" + "dockview": "^2.1.2" } } diff --git a/packages/dockview-vue/package.json b/packages/dockview-vue/package.json index ca3e602fc..afa016355 100644 --- a/packages/dockview-vue/package.json +++ b/packages/dockview-vue/package.json @@ -1,6 +1,6 @@ { "name": "dockview-vue", - "version": "2.1.1", + "version": "2.1.2", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -52,6 +52,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage" }, "dependencies": { - "dockview-core": "^2.1.1" + "dockview-core": "^2.1.2" } } diff --git a/packages/dockview/package.json b/packages/dockview/package.json index 6a560de1c..857a5d87f 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "2.1.1", + "version": "2.1.2", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,7 +54,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^2.1.1" + "dockview-core": "^2.1.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/packages/docs/package.json b/packages/docs/package.json index 7f5e08f24..4ccde5276 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "2.1.1", + "version": "2.1.2", "private": true, "scripts": { "build": "npm run build-templates && docusaurus build", @@ -38,7 +38,7 @@ "ag-grid-react": "^31.0.2", "axios": "^1.6.3", "clsx": "^2.1.0", - "dockview": "^2.1.1", + "dockview": "^2.1.2", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", "react-laag": "^2.0.5", From 6016362c5c078ed88454633aa0340198193f3f22 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 22 Dec 2024 09:41:57 +0000 Subject: [PATCH 16/83] chore: docs --- packages/docs/src/pages/popout.tsx | 5 ----- packages/docs/static/popout.html | 7 +++++++ 2 files changed, 7 insertions(+), 5 deletions(-) delete mode 100644 packages/docs/src/pages/popout.tsx create mode 100644 packages/docs/static/popout.html diff --git a/packages/docs/src/pages/popout.tsx b/packages/docs/src/pages/popout.tsx deleted file mode 100644 index d203a94ea..000000000 --- a/packages/docs/src/pages/popout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -export default function Popout() { - return

; -} diff --git a/packages/docs/static/popout.html b/packages/docs/static/popout.html new file mode 100644 index 000000000..7b3adf88d --- /dev/null +++ b/packages/docs/static/popout.html @@ -0,0 +1,7 @@ + + + + + + + From 81a904ccbc0838efbc8a6cdb93f7428f4eea1ea7 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 22 Dec 2024 10:10:26 +0000 Subject: [PATCH 17/83] bug: popout group to new group rendering bug fix --- .../dockview/dockviewComponent.spec.ts | 45 ++++++++++++++++++- .../src/dockview/dockviewComponent.ts | 4 +- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index e1ea35cbd..01c650d4f 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -4872,7 +4872,7 @@ describe('dockviewComponent', () => { ); }); - test('basic', async () => { + test('deserailize popout with no reference group', async () => { jest.useRealTimers(); const container = document.createElement('div'); @@ -5021,6 +5021,49 @@ describe('dockviewComponent', () => { expect(panel3.api.location.type).toBe('grid'); }); + test('that panel is rendered when moving from popout to new group', async () => { + const container = document.createElement('div'); + + window.open = () => setupMockWindow(); + + const dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + dockview.layout(1000, 500); + + const panel1 = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + + const panel2 = dockview.addPanel({ + id: 'panel_2', + component: 'default', + }); + + await dockview.addPopoutGroup(panel2); + + panel2.api.moveTo({ group: panel1.api.group, position: 'right' }); + + // confirm panel is rendered on DOM + expect( + panel2.group.element.querySelectorAll( + '.dv-content-container > .testpanel-panel_2' + ).length + ).toBe(1); + }); + test('move popout group of 1 panel inside grid', async () => { const container = document.createElement('div'); diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index d0d1c39aa..17e3e4f9f 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -2114,9 +2114,7 @@ export class DockviewComponent const newGroup = this.createGroupAtLocation(targetLocation); this.movingLock(() => - newGroup.model.openPanel(removedPanel, { - skipSetActive: true, - }) + newGroup.model.openPanel(removedPanel) ); this.doSetGroupAndPanelActive(newGroup); From da750e89d74890fe07d2029cddc1f1146c551ca8 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 22 Dec 2024 10:13:24 +0000 Subject: [PATCH 18/83] chore: v2.1.3 docs --- .../docs/blog/2024-12-22-dockview-2.1.3.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 packages/docs/blog/2024-12-22-dockview-2.1.3.md diff --git a/packages/docs/blog/2024-12-22-dockview-2.1.3.md b/packages/docs/blog/2024-12-22-dockview-2.1.3.md new file mode 100644 index 000000000..90d87179f --- /dev/null +++ b/packages/docs/blog/2024-12-22-dockview-2.1.3.md @@ -0,0 +1,19 @@ +--- +slug: dockview-2.1.3-release +title: Dockview 2.1.3 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +- Bug: Fix rendering issue when popout group is moved into new grid group [#799](https://github.com/mathuo/dockview/issues/799) + +## 🔥 Breaking changes + + From 8650de90aaafdc2357c234d57655f1f74d6abb6b Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 22 Dec 2024 10:17:37 +0000 Subject: [PATCH 19/83] chore(release): publish v2.1.3 --- lerna.json | 2 +- packages/dockview-angular/package.json | 4 ++-- packages/dockview-core/package.json | 2 +- packages/dockview-react/package.json | 4 ++-- packages/dockview-vue/package.json | 4 ++-- packages/dockview/package.json | 4 ++-- packages/docs/package.json | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lerna.json b/lerna.json index 401017841..1d4a1cc2c 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "2.1.2", + "version": "2.1.3", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-angular/package.json b/packages/dockview-angular/package.json index d25e4a128..e24e162d1 100644 --- a/packages/dockview-angular/package.json +++ b/packages/dockview-angular/package.json @@ -1,6 +1,6 @@ { "name": "dockview-angular", - "version": "2.1.2", + "version": "2.1.3", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^2.1.2" + "dockview-core": "^2.1.3" } } diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index 875cf6ea3..67c846531 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "2.1.2", + "version": "2.1.3", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", diff --git a/packages/dockview-react/package.json b/packages/dockview-react/package.json index 97ec1ab2e..1671bbea3 100644 --- a/packages/dockview-react/package.json +++ b/packages/dockview-react/package.json @@ -1,6 +1,6 @@ { "name": "dockview-react", - "version": "2.1.2", + "version": "2.1.3", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage" }, "dependencies": { - "dockview": "^2.1.2" + "dockview": "^2.1.3" } } diff --git a/packages/dockview-vue/package.json b/packages/dockview-vue/package.json index afa016355..b7cc5986f 100644 --- a/packages/dockview-vue/package.json +++ b/packages/dockview-vue/package.json @@ -1,6 +1,6 @@ { "name": "dockview-vue", - "version": "2.1.2", + "version": "2.1.3", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -52,6 +52,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage" }, "dependencies": { - "dockview-core": "^2.1.2" + "dockview-core": "^2.1.3" } } diff --git a/packages/dockview/package.json b/packages/dockview/package.json index 857a5d87f..813194b61 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "2.1.2", + "version": "2.1.3", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,7 +54,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^2.1.2" + "dockview-core": "^2.1.3" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/packages/docs/package.json b/packages/docs/package.json index 4ccde5276..c7f8e2274 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "2.1.2", + "version": "2.1.3", "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": "^2.1.2", + "dockview": "^2.1.3", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", "react-laag": "^2.0.5", From 4d01d4e0afa99ed2cc6772d5b85b2c952421635a Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 22 Dec 2024 18:47:09 +0000 Subject: [PATCH 20/83] chore: adjust dockview disposable --- packages/dockview-core/src/dockview/dockviewComponent.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 17e3e4f9f..c032abc6e 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -577,11 +577,6 @@ export class DockviewComponent this.updateWatermark(); } - override dispose(): void { - this.clear(); // explicitly clear the layout before cleaning up - super.dispose(); - } - override setVisible(panel: DockviewGroupPanel, visible: boolean): void { switch (panel.api.location.type) { case 'grid': From 296b49180e999af48f0674a61f5e8875cd94d2fe Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 22 Dec 2024 18:55:10 +0000 Subject: [PATCH 21/83] bug: fix overlay container for popout to floating --- .../dockview/dockviewComponent.spec.ts | 23 ++++++++++++++++++- .../src/dockview/dockviewComponent.ts | 17 +++++++------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 01c650d4f..2c4f09c10 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -5052,8 +5052,13 @@ describe('dockviewComponent', () => { component: 'default', }); - await dockview.addPopoutGroup(panel2); + const panel3 = dockview.addPanel({ + id: 'panel_3', + component: 'default', + renderer: 'always', + }); + await dockview.addPopoutGroup(panel2); panel2.api.moveTo({ group: panel1.api.group, position: 'right' }); // confirm panel is rendered on DOM @@ -5062,6 +5067,22 @@ describe('dockviewComponent', () => { '.dv-content-container > .testpanel-panel_2' ).length ).toBe(1); + + await dockview.addPopoutGroup(panel3); + panel3.api.moveTo({ group: panel1.api.group, position: 'right' }); + + // confirm panel is rendered to always overlay container + expect( + dockview.element.querySelectorAll( + '.dv-render-overlay > .testpanel-panel_3' + ).length + ).toBe(1); + expect( + panel2.group.element.querySelectorAll( + '.dv-content-container > .testpanel-panel_3' + ).length + ).toBe(0); + expect(dockview.element); }); test('move popout group of 1 panel inside grid', async () => { diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index c032abc6e..63915751d 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -842,32 +842,31 @@ export class DockviewComponent }); } } else if (this.getPanel(group.id)) { - const removedGroup = group; + group.model.renderContainer = + this.overlayRenderContainer; + returnedGroup = group; if (floatingBox) { - this.addFloatingGroup(removedGroup, { + this.addFloatingGroup(group, { height: floatingBox.height, width: floatingBox.width, position: floatingBox, }); } else { - this.doRemoveGroup(removedGroup, { + this.doRemoveGroup(group, { skipDispose: true, skipActive: true, skipPopoutReturn: true, }); - removedGroup.model.renderContainer = - this.overlayRenderContainer; - removedGroup.model.location = { type: 'grid' }; - returnedGroup = removedGroup; + group.model.location = { type: 'grid' }; this.movingLock(() => { // suppress group add events since the group already exists - this.doAddGroup(removedGroup, [0]); + this.doAddGroup(group, [0]); }); } - this.doSetGroupAndPanelActive(removedGroup); + this.doSetGroupAndPanelActive(group); } }) ); From 491872ea75a677b52a64798fb18dc663d4627934 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:19:38 +0000 Subject: [PATCH 22/83] chore: v2.1.4 docs --- .../docs/blog/2024-12-23-dockview-2.1.4.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 packages/docs/blog/2024-12-23-dockview-2.1.4.md diff --git a/packages/docs/blog/2024-12-23-dockview-2.1.4.md b/packages/docs/blog/2024-12-23-dockview-2.1.4.md new file mode 100644 index 000000000..731c69478 --- /dev/null +++ b/packages/docs/blog/2024-12-23-dockview-2.1.4.md @@ -0,0 +1,20 @@ +--- +slug: dockview-2.1.4-release +title: Dockview 2.1.4 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +- Maintenance: Cleanup resource dispose [#801](https://github.com/mathuo/dockview/issues/801) +- Bug: Fix for using popout groups with `always` rendering [#803](https://github.com/mathuo/dockview/issues/803) + +## 🔥 Breaking changes + + From e1304ce69462ccbe9ed5a3723369654eb6e8ec19 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:20:24 +0000 Subject: [PATCH 23/83] chore: docs --- .../react/dockview/demo-dockview/src/app.scss | 147 ++++++++++-------- .../react/dockview/demo-dockview/src/app.tsx | 112 +++++++------ .../demo-dockview/src/gridActions.tsx | 15 +- .../demo-dockview/src/mapboxPanel.tsx | 28 ++++ .../demo-dockview/src/panelActions.tsx | 9 +- 5 files changed, 188 insertions(+), 123 deletions(-) create mode 100644 packages/docs/sandboxes/react/dockview/demo-dockview/src/mapboxPanel.tsx diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.scss b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.scss index 6abc9a64d..57549c075 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.scss +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.scss @@ -1,33 +1,29 @@ -.group-control { - .action { - padding: 4px; - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - font-size: 18px; - cursor: pointer; +.dockview-demo { + .group-control { + .action { + padding: 4px; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + font-size: 18px; + cursor: pointer; - &:hover { - border-radius: 2px; - background-color: var(--dv-icon-hover-background-color); + &:hover { + border-radius: 2px; + background-color: var(--dv-icon-hover-background-color); + } } } -} -.data-table { - table { - font-size: 11px; - th { - padding: 0px 8px; + .data-table { + table { + font-size: 11px; + th { + padding: 0px 8px; + } } } -} - -.action-container { - display: flex; - padding: 4px 0px; - overflow: auto; button { height: 25px; @@ -40,57 +36,78 @@ cursor: pointer; outline: 1px solid #4c65d4; + &:focus { + outline: 1px solid #4c65d4 !important; + } + &:hover { background-color: #222e62; } } - .text-button { - margin: 0px 4px; - } - - .button-action { - margin: 0px 4px; - // display: flex; - - .selected { - background-color: #4864dc; - } - } - - .button-group { - button { - margin-right: 0px; - } - } - - .demo-button { - min-width: 50px; - padding: 0px 2px; - border-radius: 0px; - display: flex; - flex-grow: 1; - align-items: center; + input { outline: 1px solid #4c65d4; - } - - .demo-icon-button { - outline: 1px solid #4c65d4; - flex-grow: 1; - display: flex; - align-items: center; - border-radius: 0px; - padding: 0px 4px; border: none; - cursor: pointer; + margin: 0px; + height: 25px; - &:disabled { - color: gray; - cursor: help; + &:focus { + outline: 1px solid #4c65d4 !important; + } + } + + .action-container { + display: flex; + padding: 4px; + overflow: auto; + + .text-button { + margin: 0px 4px; } - span { - font-size: 16px; + .button-action { + margin: 0px 4px; + // display: flex; + + .selected { + background-color: #4864dc; + } + } + + .button-group { + button { + margin-right: 0px; + } + } + + .demo-button { + min-width: 50px; + padding: 0px 2px; + border-radius: 0px; + display: flex; + flex-grow: 1; + align-items: center; + outline: 1px solid #4c65d4; + } + + .demo-icon-button { + outline: 1px solid #4c65d4; + flex-grow: 1; + display: flex; + align-items: center; + border-radius: 0px; + padding: 0px 4px; + border: none; + cursor: pointer; + + &:disabled { + color: gray; + cursor: help; + } + + span { + font-size: 16px; + } } } } diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx index a22bbf159..a9f4ea97c 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx @@ -89,7 +89,10 @@ const components = { event.api.addPanel({ id: 'panel_3', component: 'default', - floating: true, + }); + + event.api.onDidRemovePanel((e) => { + console.log('remove', e); }); }} className={'dockview-theme-abyss'} @@ -264,6 +267,7 @@ const DockviewDemo = (props: { theme?: string }) => { return (
{ width: '400px', backgroundColor: 'black', color: 'white', - overflow: 'auto', + overflow: 'hidden', fontFamily: 'monospace', marginLeft: '10px', flexShrink: 0, + display: 'flex', + flexDirection: 'column', }} > - {logLines.map((line, i) => { - return ( -
- + {logLines.map((line, i) => { + return ( +
- {logLines.length - i} - - - {line.timestamp && ( - - {line.timestamp - .toISOString() - .substring(11, 23)} - - )} - {line.text} - -
- ); - })} + + {logLines.length - i} + + + {line.timestamp && ( + + {line.timestamp + .toISOString() + .substring(11, 23)} + + )} + {line.text} + +
+ ); + })} +
+
+ +
)} diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx index f1b2a0eeb..40e57b2fa 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx @@ -132,7 +132,7 @@ export const GridActions = (props: { const popover = usePopover(); - const onAddPanel = (options?: { advanced: boolean }) => { + const onAddPanel = (options?: { advanced?: boolean; nested?: boolean }) => { if (options?.advanced) { popover.open(({ close }) => { return ; @@ -140,7 +140,7 @@ export const GridActions = (props: { } else { props.api?.addPanel({ id: `id_${Date.now().toString()}`, - component: 'default', + component: options?.nested ? 'nested' : 'default', title: `Tab ${nextId()}`, renderer: 'always', }); @@ -170,6 +170,12 @@ export const GridActions = (props: { tune + @@ -198,8 +204,8 @@ export const GridActions = (props: { Reset -
- Group Gap +
+ Grid Gap setGap(Number(event.target.value))} /> +
); diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/mapboxPanel.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/mapboxPanel.tsx new file mode 100644 index 000000000..3393133a3 --- /dev/null +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/mapboxPanel.tsx @@ -0,0 +1,28 @@ +import { IDockviewPanelProps } from 'dockview'; +import * as React from 'react'; +import Map from 'react-map-gl'; + +export const MapboxPanel = (props: IDockviewPanelProps) => { + React.useEffect(() => { + const subscription = props.api.onDidLocationChange((e) => { + const isPopout = e.location.type === 'popout'; + }); + + return () => subscription.dispose(); + }, [props.api]); + + return ( +
+ +
+ ); +}; diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/panelActions.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/panelActions.tsx index c81a63077..276aaa94e 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/panelActions.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/panelActions.tsx @@ -74,14 +74,7 @@ const PanelAction = (props: { onClick={() => { const panel = props.api.getPanel(props.panelId); if (panel) { - props.api.addFloatingGroup(panel, { - position: { - width: 400, - height: 300, - bottom: 50, - right: 50, - }, - }); + props.api.addFloatingGroup(panel); } }} > From 32fa1511d8c64a812cdbe5f7c07aaa1dc01815e3 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:20:36 +0000 Subject: [PATCH 24/83] chore(release): publish v2.1.4 --- lerna.json | 2 +- packages/dockview-angular/package.json | 4 ++-- packages/dockview-core/package.json | 2 +- packages/dockview-react/package.json | 4 ++-- packages/dockview-vue/package.json | 4 ++-- packages/dockview/package.json | 4 ++-- packages/docs/package.json | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lerna.json b/lerna.json index 1d4a1cc2c..75683ed53 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "2.1.3", + "version": "2.1.4", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-angular/package.json b/packages/dockview-angular/package.json index e24e162d1..7298c7316 100644 --- a/packages/dockview-angular/package.json +++ b/packages/dockview-angular/package.json @@ -1,6 +1,6 @@ { "name": "dockview-angular", - "version": "2.1.3", + "version": "2.1.4", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^2.1.3" + "dockview-core": "^2.1.4" } } diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index 67c846531..859653dbf 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "2.1.3", + "version": "2.1.4", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", diff --git a/packages/dockview-react/package.json b/packages/dockview-react/package.json index 1671bbea3..4d6316852 100644 --- a/packages/dockview-react/package.json +++ b/packages/dockview-react/package.json @@ -1,6 +1,6 @@ { "name": "dockview-react", - "version": "2.1.3", + "version": "2.1.4", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage" }, "dependencies": { - "dockview": "^2.1.3" + "dockview": "^2.1.4" } } diff --git a/packages/dockview-vue/package.json b/packages/dockview-vue/package.json index b7cc5986f..527940656 100644 --- a/packages/dockview-vue/package.json +++ b/packages/dockview-vue/package.json @@ -1,6 +1,6 @@ { "name": "dockview-vue", - "version": "2.1.3", + "version": "2.1.4", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -52,6 +52,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage" }, "dependencies": { - "dockview-core": "^2.1.3" + "dockview-core": "^2.1.4" } } diff --git a/packages/dockview/package.json b/packages/dockview/package.json index 813194b61..a65944dcf 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "2.1.3", + "version": "2.1.4", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,7 +54,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^2.1.3" + "dockview-core": "^2.1.4" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/packages/docs/package.json b/packages/docs/package.json index c7f8e2274..f605b50c5 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "2.1.3", + "version": "2.1.4", "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": "^2.1.3", + "dockview": "^2.1.4", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", "react-laag": "^2.0.5", From b3ce3e0366b659c4f999ed8d3a45c5be34c63fb7 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:24:20 +0000 Subject: [PATCH 25/83] feat: rename class --- .../src/__tests__/dockview/headerActionsRenderer.spec.ts | 2 +- packages/dockview/src/dockview/headerActionsRenderer.ts | 2 +- packages/dockview/src/dockview/reactContentPart.ts | 2 +- packages/dockview/src/dockview/reactHeaderPart.ts | 2 +- packages/dockview/src/dockview/reactWatermarkPart.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/dockview/src/__tests__/dockview/headerActionsRenderer.spec.ts b/packages/dockview/src/__tests__/dockview/headerActionsRenderer.spec.ts index ec6015403..be273602f 100644 --- a/packages/dockview/src/__tests__/dockview/headerActionsRenderer.spec.ts +++ b/packages/dockview/src/__tests__/dockview/headerActionsRenderer.spec.ts @@ -37,7 +37,7 @@ describe('headerActionsRenderer', () => { ); expect(cut.element.childNodes.length).toBe(0); - expect(cut.element.className).toBe('dockview-react-part'); + expect(cut.element.className).toBe('dv-react-part'); expect(cut.part).toBeUndefined(); cut.init({ diff --git a/packages/dockview/src/dockview/headerActionsRenderer.ts b/packages/dockview/src/dockview/headerActionsRenderer.ts index c856b1bbc..fcf4f273d 100644 --- a/packages/dockview/src/dockview/headerActionsRenderer.ts +++ b/packages/dockview/src/dockview/headerActionsRenderer.ts @@ -30,7 +30,7 @@ export class ReactHeaderActionsRendererPart implements IHeaderActionsRenderer { private readonly _group: DockviewGroupPanel ) { this._element = document.createElement('div'); - this._element.className = 'dockview-react-part'; + this._element.className = 'dv-react-part'; this._element.style.height = '100%'; this._element.style.width = '100%'; } diff --git a/packages/dockview/src/dockview/reactContentPart.ts b/packages/dockview/src/dockview/reactContentPart.ts index 46d8b3bb4..984a911cb 100644 --- a/packages/dockview/src/dockview/reactContentPart.ts +++ b/packages/dockview/src/dockview/reactContentPart.ts @@ -29,7 +29,7 @@ export class ReactPanelContentPart implements IContentRenderer { private readonly reactPortalStore: ReactPortalStore ) { this._element = document.createElement('div'); - this._element.className = 'dockview-react-part'; + this._element.className = 'dv-react-part'; this._element.style.height = '100%'; this._element.style.width = '100%'; } diff --git a/packages/dockview/src/dockview/reactHeaderPart.ts b/packages/dockview/src/dockview/reactHeaderPart.ts index 772a4a814..4ef8738e9 100644 --- a/packages/dockview/src/dockview/reactHeaderPart.ts +++ b/packages/dockview/src/dockview/reactHeaderPart.ts @@ -21,7 +21,7 @@ export class ReactPanelHeaderPart implements ITabRenderer { private readonly reactPortalStore: ReactPortalStore ) { this._element = document.createElement('div'); - this._element.className = 'dockview-react-part'; + this._element.className = 'dv-react-part'; this._element.style.height = '100%'; this._element.style.width = '100%'; } diff --git a/packages/dockview/src/dockview/reactWatermarkPart.ts b/packages/dockview/src/dockview/reactWatermarkPart.ts index f4b95c02e..20fdf3f39 100644 --- a/packages/dockview/src/dockview/reactWatermarkPart.ts +++ b/packages/dockview/src/dockview/reactWatermarkPart.ts @@ -23,7 +23,7 @@ export class ReactWatermarkPart implements IWatermarkRenderer { private readonly reactPortalStore: ReactPortalStore ) { this._element = document.createElement('div'); - this._element.className = 'dockview-react-part'; + this._element.className = 'dv-react-part'; this._element.style.height = '100%'; this._element.style.width = '100%'; } From 34798282371b9d73276fc22c98f48b805d6a85a1 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:52:53 +0000 Subject: [PATCH 26/83] chore: vue3 peerDependency --- packages/dockview-vue/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/dockview-vue/package.json b/packages/dockview-vue/package.json index 527940656..04395a823 100644 --- a/packages/dockview-vue/package.json +++ b/packages/dockview-vue/package.json @@ -53,5 +53,8 @@ }, "dependencies": { "dockview-core": "^2.1.4" + }, + "peerDependencies": { + "vue": "^3.4.0" } } From 7df9c510bd7f684ce54f6bb9dc7442525a508ff2 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:53:20 +0000 Subject: [PATCH 27/83] chore: clean up project dependencies --- package.json | 3 --- yarn.lock | 12 ------------ 2 files changed, 15 deletions(-) diff --git a/package.json b/package.json index 3d17caa64..f9dcde43f 100644 --- a/package.json +++ b/package.json @@ -77,8 +77,5 @@ }, "engines": { "node": ">=18.0" - }, - "dependencies": { - "ag-grid-vue3": "^31.1.1" } } diff --git a/yarn.lock b/yarn.lock index 204fe8376..1232e119a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5282,11 +5282,6 @@ address@^1.0.1, address@^1.1.2: resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e" integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== -ag-grid-community@31.1.1: - version "31.1.1" - resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-31.1.1.tgz#212fc3e358d4be1865bc4618f6d0d865faaed385" - integrity sha512-tiQZ7VQ07yJScTMIQpaYoUMPgiyXMwYDcwTxe4riRrcYGTg0e258XEihoPUZFejR60P1fYWMxdJaR2JUnyhGrg== - ag-grid-community@^31.0.2, ag-grid-community@~31.0.2: version "31.0.2" resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-31.0.2.tgz#8421c4e23e29205628281c1258808e83dccdada2" @@ -5300,13 +5295,6 @@ ag-grid-react@^31.0.2: ag-grid-community "~31.0.2" prop-types "^15.8.1" -ag-grid-vue3@^31.1.1: - version "31.1.1" - resolved "https://registry.yarnpkg.com/ag-grid-vue3/-/ag-grid-vue3-31.1.1.tgz#0382632cf521138c532b48c9b985c3951d69e788" - integrity sha512-GjR4/6JUWQ0VsdmcxSN5ks1vH+wB7lLjX1CbskPqNfD69/fb9l7Eg2oGAgqNJo8b87W87jzhg9RguOx5bDpsZQ== - dependencies: - ag-grid-community "31.1.1" - agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" From fade057412040eb38f0a16ecc7b13a56396ba7cb Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:38:56 +0000 Subject: [PATCH 28/83] feat: migrate to generic construction --- .../gridview/gridviewComponent.spec.ts | 283 +++++++++++++++--- .../__tests__/panel/componentFactory.spec.ts | 102 ------- .../src/__tests__/paneview/paneview.spec.ts | 10 +- .../paneview/paneviewComponent.spec.ts | 163 ++++++---- .../splitview/splitviewComponent.spec.ts | 231 +++++++++----- .../dockview-core/src/dockview/options.ts | 2 +- .../src/gridview/gridviewComponent.ts | 49 +-- .../dockview-core/src/gridview/options.ts | 43 ++- packages/dockview-core/src/index.ts | 17 +- .../src/panel/componentFactory.ts | 58 ---- .../src/paneview/defaultPaneviewHeader.ts | 7 +- .../dockview-core/src/paneview/options.ts | 50 ++-- .../src/paneview/paneviewComponent.ts | 105 ++----- .../src/paneview/paneviewPanel.ts | 16 +- .../dockview-core/src/splitview/options.ts | 39 ++- .../dockview-core/src/splitview/splitview.ts | 10 +- .../src/splitview/splitviewComponent.ts | 41 +-- .../dockview-vue/src/dockview/dockview.vue | 6 +- packages/dockview/src/dockview/dockview.tsx | 9 +- packages/dockview/src/gridview/gridview.tsx | 94 ++++-- packages/dockview/src/paneview/paneview.tsx | 127 +++++--- packages/dockview/src/paneview/view.ts | 4 +- packages/dockview/src/splitview/splitview.tsx | 100 +++++-- 23 files changed, 922 insertions(+), 644 deletions(-) delete mode 100644 packages/dockview-core/src/__tests__/panel/componentFactory.spec.ts delete mode 100644 packages/dockview-core/src/panel/componentFactory.ts diff --git a/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts b/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts index ec5f306f8..5f9febc5c 100644 --- a/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts @@ -36,7 +36,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, className: 'test-a test-b', }); @@ -51,7 +58,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -70,7 +84,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -100,7 +121,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -160,7 +188,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -287,7 +322,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(1000, 1000); @@ -323,7 +365,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -446,7 +495,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -475,7 +531,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -533,7 +596,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -591,7 +661,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -667,7 +744,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -761,7 +845,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -855,7 +946,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -949,7 +1047,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -1073,7 +1178,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -1197,7 +1309,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -1323,7 +1442,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -1447,7 +1573,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -1571,7 +1704,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.fromJSON({ @@ -1698,7 +1838,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(1000, 1000); @@ -1728,7 +1875,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(1000, 1000); @@ -1757,7 +1911,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(1000, 1000); @@ -1795,7 +1956,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); let addGroup: GridviewPanel[] = []; @@ -1917,7 +2085,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(1600, 800); @@ -2043,7 +2218,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(6000, 5000); @@ -2318,7 +2500,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(5000, 6000); @@ -2591,7 +2780,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error(`unsupported panel '${options.name}'`); + } + }, }); let el = gridview.element.querySelector('.dv-view-container'); @@ -2655,9 +2851,7 @@ describe('gridview', () => { }, activePanel: 'panel_1', }); - }).toThrow( - "Cannot create 'panel_1', no component 'somethingBad' provided" - ); + }).toThrow("unsupported panel 'somethingBad'"); expect(gridview.groups.length).toBe(0); @@ -2670,7 +2864,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); expect(gridview.disableResizing).toBeFalsy(); @@ -2680,7 +2881,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, disableAutoResizing: true, }); @@ -2691,7 +2899,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, disableAutoResizing: true, }); gridview.layout(1000, 1000); diff --git a/packages/dockview-core/src/__tests__/panel/componentFactory.spec.ts b/packages/dockview-core/src/__tests__/panel/componentFactory.spec.ts deleted file mode 100644 index 4e330056a..000000000 --- a/packages/dockview-core/src/__tests__/panel/componentFactory.spec.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { createComponent } from '../../panel/componentFactory'; - -describe('componentFactory', () => { - describe('createComponent', () => { - test('valid component and framework component', () => { - const mock = jest.fn(); - const mock2 = jest.fn(); - - expect(() => - createComponent( - 'id-1', - 'component-1', - { 'component-1': mock }, - { 'component-1': mock2 } - ) - ).toThrow( - "Cannot create 'id-1'. component 'component-1' registered as both a component and frameworkComponent" - ); - }); - - test('valid framework component but no factory', () => { - const mock = jest.fn(); - - expect(() => - createComponent( - 'id-1', - 'component-1', - {}, - { 'component-1': mock } - ) - ).toThrow( - "Cannot create 'id-1' for framework component 'component-1'. you must register a frameworkPanelWrapper to use framework components" - ); - }); - - test('valid framework component', () => { - const component = jest.fn(); - const createComponentFn = jest - .fn() - .mockImplementation(() => component); - const frameworkComponent = jest.fn(); - - expect( - createComponent( - 'id-1', - 'component-1', - {}, - { 'component-1': frameworkComponent }, - { - createComponent: createComponentFn, - } - ) - ).toBe(component); - - expect(createComponentFn).toHaveBeenCalledWith( - 'id-1', - 'component-1', - frameworkComponent - ); - }); - - test('no valid component with fallback', () => { - const mock = jest.fn(); - - expect( - createComponent( - 'id-1', - 'component-1', - {}, - {}, - { - createComponent: () => null, - }, - () => mock - ) - ).toBe(mock); - }); - - test('no valid component', () => { - expect(() => - createComponent('id-1', 'component-1', {}, {}) - ).toThrow( - "Cannot create 'id-1', no component 'component-1' provided" - ); - }); - - test('valid component', () => { - const component = jest.fn(); - - const componentResult = createComponent( - 'id-1', - 'component-1', - { 'component-1': component }, - {} - ); - - expect(component).toHaveBeenCalled(); - - expect(componentResult instanceof component).toBeTruthy(); - }); - }); -}); diff --git a/packages/dockview-core/src/__tests__/paneview/paneview.spec.ts b/packages/dockview-core/src/__tests__/paneview/paneview.spec.ts index b74c81e2b..5bc8720d8 100644 --- a/packages/dockview-core/src/__tests__/paneview/paneview.spec.ts +++ b/packages/dockview-core/src/__tests__/paneview/paneview.spec.ts @@ -1,14 +1,10 @@ import { CompositeDisposable } from '../../lifecycle'; import { Paneview } from '../../paneview/paneview'; -import { - IPaneBodyPart, - IPaneHeaderPart, - PaneviewPanel, -} from '../../paneview/paneviewPanel'; +import { IPanePart, PaneviewPanel } from '../../paneview/paneviewPanel'; import { Orientation } from '../../splitview/splitview'; class TestPanel extends PaneviewPanel { - protected getBodyComponent(): IPaneBodyPart { + protected getBodyComponent(): IPanePart { return { element: document.createElement('div'), update: () => { @@ -23,7 +19,7 @@ class TestPanel extends PaneviewPanel { }; } - protected getHeaderComponent(): IPaneHeaderPart { + protected getHeaderComponent(): IPanePart { return { element: document.createElement('div'), update: () => { diff --git a/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts b/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts index 1c5a77da7..77a58712b 100644 --- a/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts @@ -4,8 +4,7 @@ import { PanelUpdateEvent } from '../../panel/types'; import { PaneviewComponent } from '../../paneview/paneviewComponent'; import { PaneviewPanel, - IPaneBodyPart, - IPaneHeaderPart, + IPanePart, PanePanelComponentInitParameter, } from '../../paneview/paneviewPanel'; import { Orientation } from '../../splitview/splitview'; @@ -16,7 +15,7 @@ class TestPanel extends PaneviewPanel { } getHeaderComponent() { - return new (class Header implements IPaneHeaderPart { + return new (class Header implements IPanePart { private _element: HTMLElement = document.createElement('div'); get element() { @@ -38,7 +37,7 @@ class TestPanel extends PaneviewPanel { } getBodyComponent() { - return new (class Header implements IPaneBodyPart { + return new (class Header implements IPanePart { private _element: HTMLElement = document.createElement('div'); get element() { @@ -72,8 +71,13 @@ describe('componentPaneview', () => { const disposables = new CompositeDisposable(); const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -81,12 +85,12 @@ describe('componentPaneview', () => { paneview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }); paneview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel2', }); @@ -144,8 +148,13 @@ describe('componentPaneview', () => { test('serialization', () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -156,7 +165,7 @@ describe('componentPaneview', () => { size: 1, data: { id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }, expanded: true, @@ -165,7 +174,7 @@ describe('componentPaneview', () => { size: 2, data: { id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }, expanded: false, @@ -174,7 +183,7 @@ describe('componentPaneview', () => { size: 3, data: { id: 'panel3', - component: 'testPanel', + component: 'default', title: 'Panel 3', }, }, @@ -220,7 +229,7 @@ describe('componentPaneview', () => { size: 756, data: { id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }, expanded: true, @@ -230,7 +239,7 @@ describe('componentPaneview', () => { size: 22, data: { id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }, expanded: false, @@ -240,7 +249,7 @@ describe('componentPaneview', () => { size: 22, data: { id: 'panel3', - component: 'testPanel', + component: 'default', title: 'Panel 3', }, expanded: false, @@ -252,20 +261,25 @@ describe('componentPaneview', () => { test('toJSON shouldnt fire any layout events', () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); paneview.layout(1000, 1000); paneview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }); paneview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }); @@ -283,8 +297,13 @@ describe('componentPaneview', () => { expect(container.childNodes.length).toBe(0); const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -292,12 +311,12 @@ describe('componentPaneview', () => { paneview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }); paneview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }); @@ -310,8 +329,13 @@ describe('componentPaneview', () => { test('panel is disposed of when component is disposed', () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -319,12 +343,12 @@ describe('componentPaneview', () => { paneview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }); paneview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }); @@ -342,8 +366,13 @@ describe('componentPaneview', () => { test('panel is disposed of when removed', () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -351,12 +380,12 @@ describe('componentPaneview', () => { paneview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }); paneview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }); @@ -374,8 +403,13 @@ describe('componentPaneview', () => { test('panel is disposed of when fromJSON is called', () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -383,12 +417,12 @@ describe('componentPaneview', () => { paneview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }); paneview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }); @@ -406,8 +440,13 @@ describe('componentPaneview', () => { test('that fromJSON layouts are resized to the current dimensions', async () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -420,7 +459,7 @@ describe('componentPaneview', () => { size: 1, data: { id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }, expanded: true, @@ -429,7 +468,7 @@ describe('componentPaneview', () => { size: 2, data: { id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }, expanded: true, @@ -438,7 +477,7 @@ describe('componentPaneview', () => { size: 3, data: { id: 'panel3', - component: 'testPanel', + component: 'default', title: 'Panel 3', }, expanded: true, @@ -454,7 +493,7 @@ describe('componentPaneview', () => { size: 122, data: { id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }, expanded: true, @@ -464,7 +503,7 @@ describe('componentPaneview', () => { size: 122, data: { id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }, expanded: true, @@ -474,7 +513,7 @@ describe('componentPaneview', () => { size: 356, data: { id: 'panel3', - component: 'testPanel', + component: 'default', title: 'Panel 3', }, expanded: true, @@ -486,8 +525,13 @@ describe('componentPaneview', () => { test('that disableAutoResizing is false by default', () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -496,8 +540,13 @@ describe('componentPaneview', () => { test('that disableAutoResizing can be enabled', () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, disableAutoResizing: true, }); @@ -507,8 +556,13 @@ describe('componentPaneview', () => { test('that setVisible toggles visiblity', () => { const paneview = new PaneviewComponent(container, { - components: { - default: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, disableAutoResizing: true, }); @@ -540,8 +594,13 @@ describe('componentPaneview', () => { test('update className', () => { const paneview = new PaneviewComponent(container, { - components: { - default: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, disableAutoResizing: true, className: 'test-a test-b', diff --git a/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts b/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts index 4c7ed46f3..6ab1415b2 100644 --- a/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts @@ -31,19 +31,24 @@ describe('componentSplitview', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(600, 400); const panel1 = splitview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', }); const panel2 = splitview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', }); splitview.movePanel(0, 1); @@ -67,15 +72,20 @@ describe('componentSplitview', () => { test('remove panel', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(600, 400); - splitview.addPanel({ id: 'panel1', component: 'testPanel' }); - splitview.addPanel({ id: 'panel2', component: 'testPanel' }); - splitview.addPanel({ id: 'panel3', component: 'testPanel' }); + splitview.addPanel({ id: 'panel1', component: 'default' }); + splitview.addPanel({ id: 'panel2', component: 'default' }); + splitview.addPanel({ id: 'panel3', component: 'default' }); const panel1 = splitview.getPanel('panel1')!; const panel2 = splitview.getPanel('panel2')!; @@ -102,8 +112,13 @@ describe('componentSplitview', () => { test('horizontal dimensions', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(600, 400); @@ -115,8 +130,13 @@ describe('componentSplitview', () => { test('vertical dimensions', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(600, 400); @@ -128,15 +148,20 @@ describe('componentSplitview', () => { test('api resize', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(400, 600); - splitview.addPanel({ id: 'panel1', component: 'testPanel' }); - splitview.addPanel({ id: 'panel2', component: 'testPanel' }); - splitview.addPanel({ id: 'panel3', component: 'testPanel' }); + splitview.addPanel({ id: 'panel1', component: 'default' }); + splitview.addPanel({ id: 'panel2', component: 'default' }); + splitview.addPanel({ id: 'panel3', component: 'default' }); const panel1 = splitview.getPanel('panel1')!; const panel2 = splitview.getPanel('panel2')!; @@ -180,13 +205,18 @@ describe('componentSplitview', () => { test('api', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(600, 400); - splitview.addPanel({ id: 'panel1', component: 'testPanel' }); + splitview.addPanel({ id: 'panel1', component: 'default' }); const panel1 = splitview.getPanel('panel1'); @@ -197,7 +227,7 @@ describe('componentSplitview', () => { // expect(panel1?.api.isFocused).toBeFalsy(); expect(panel1!.api.isVisible).toBeTruthy(); - splitview.addPanel({ id: 'panel2', component: 'testPanel' }); + splitview.addPanel({ id: 'panel2', component: 'default' }); const panel2 = splitview.getPanel('panel2'); @@ -221,15 +251,20 @@ describe('componentSplitview', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(300, 200); - splitview.addPanel({ id: 'panel1', component: 'testPanel' }); - splitview.addPanel({ id: 'panel2', component: 'testPanel' }); + splitview.addPanel({ id: 'panel1', component: 'default' }); + splitview.addPanel({ id: 'panel2', component: 'default' }); const panel1 = splitview.getPanel('panel1') as SplitviewPanel; const panel2 = splitview.getPanel('panel2') as SplitviewPanel; @@ -272,15 +307,20 @@ describe('componentSplitview', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(300, 200); - splitview.addPanel({ id: 'panel1', component: 'testPanel' }); - splitview.addPanel({ id: 'panel2', component: 'testPanel' }); + splitview.addPanel({ id: 'panel1', component: 'default' }); + splitview.addPanel({ id: 'panel2', component: 'default' }); const panel1 = splitview.getPanel('panel1') as SplitviewPanel; const panel2 = splitview.getPanel('panel2') as SplitviewPanel; @@ -321,8 +361,13 @@ describe('componentSplitview', () => { test('serialization', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(400, 6); @@ -331,15 +376,15 @@ describe('componentSplitview', () => { views: [ { size: 1, - data: { id: 'panel1', component: 'testPanel' }, + data: { id: 'panel1', component: 'default' }, snap: false, }, { size: 2, - data: { id: 'panel2', component: 'testPanel' }, + data: { id: 'panel2', component: 'default' }, snap: true, }, - { size: 3, data: { id: 'panel3', component: 'testPanel' } }, + { size: 3, data: { id: 'panel3', component: 'default' } }, ], size: 6, orientation: Orientation.VERTICAL, @@ -352,17 +397,17 @@ describe('componentSplitview', () => { views: [ { size: 1, - data: { id: 'panel1', component: 'testPanel' }, + data: { id: 'panel1', component: 'default' }, snap: false, }, { size: 2, - data: { id: 'panel2', component: 'testPanel' }, + data: { id: 'panel2', component: 'default' }, snap: true, }, { size: 3, - data: { id: 'panel3', component: 'testPanel' }, + data: { id: 'panel3', component: 'default' }, snap: false, }, ], @@ -375,8 +420,13 @@ describe('componentSplitview', () => { test('toJSON shouldnt fire any layout events', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -384,11 +434,11 @@ describe('componentSplitview', () => { splitview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', }); splitview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', }); const disposable = splitview.onDidLayoutChange(() => { @@ -406,8 +456,13 @@ describe('componentSplitview', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -415,11 +470,11 @@ describe('componentSplitview', () => { splitview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', }); splitview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', }); expect(container.childNodes.length).toBeGreaterThan(0); @@ -432,8 +487,13 @@ describe('componentSplitview', () => { test('panel is disposed of when component is disposed', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - default: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -463,8 +523,13 @@ describe('componentSplitview', () => { test('panel is disposed of when removed', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - default: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -494,8 +559,13 @@ describe('componentSplitview', () => { test('panel is disposed of when fromJSON is called', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - default: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -529,8 +599,13 @@ describe('componentSplitview', () => { test('that fromJSON layouts are resized to the current dimensions', async () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(400, 600); @@ -539,15 +614,15 @@ describe('componentSplitview', () => { views: [ { size: 1, - data: { id: 'panel1', component: 'testPanel' }, + data: { id: 'panel1', component: 'default' }, snap: false, }, { size: 2, - data: { id: 'panel2', component: 'testPanel' }, + data: { id: 'panel2', component: 'default' }, snap: true, }, - { size: 3, data: { id: 'panel3', component: 'testPanel' } }, + { size: 3, data: { id: 'panel3', component: 'default' } }, ], size: 6, orientation: Orientation.VERTICAL, @@ -558,17 +633,17 @@ describe('componentSplitview', () => { views: [ { size: 100, - data: { id: 'panel1', component: 'testPanel' }, + data: { id: 'panel1', component: 'default' }, snap: false, }, { size: 200, - data: { id: 'panel2', component: 'testPanel' }, + data: { id: 'panel2', component: 'default' }, snap: true, }, { size: 300, - data: { id: 'panel3', component: 'testPanel' }, + data: { id: 'panel3', component: 'default' }, snap: false, }, ], @@ -581,8 +656,13 @@ describe('componentSplitview', () => { test('that disableAutoResizing is false by default', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -592,8 +672,13 @@ describe('componentSplitview', () => { test('that disableAutoResizing can be enabled', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, disableAutoResizing: true, }); @@ -604,8 +689,13 @@ describe('componentSplitview', () => { test('that setVisible toggles visiblity', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - default: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -635,8 +725,13 @@ describe('componentSplitview', () => { test('update className', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - default: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, className: 'test-a test-b', }); diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 3e87923a2..05175190a 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -96,7 +96,7 @@ export class DockviewUnhandledDragOverEvent implements DockviewDndOverlayEvent { } } -export const PROPERTY_KEYS: (keyof DockviewOptions)[] = (() => { +export const PROPERTY_KEYS_DOCKVIEW: (keyof DockviewOptions)[] = (() => { /** * by readong the keys from an empty value object TypeScript will error * when we add or remove new properties to `DockviewOptions` diff --git a/packages/dockview-core/src/gridview/gridviewComponent.ts b/packages/dockview-core/src/gridview/gridviewComponent.ts index a0ea22288..00084ce44 100644 --- a/packages/dockview-core/src/gridview/gridviewComponent.ts +++ b/packages/dockview-core/src/gridview/gridviewComponent.ts @@ -23,7 +23,6 @@ import { } from './gridviewPanel'; import { BaseComponentOptions, Parameters } from '../panel/types'; import { Orientation, Sizing } from '../splitview/splitview'; -import { createComponent } from '../panel/componentFactory'; import { Emitter, Event } from '../events'; import { Position } from '../dnd/droptarget'; @@ -116,9 +115,11 @@ export class GridviewComponent constructor(parentElement: HTMLElement, options: GridviewComponentOptions) { super(parentElement, { - proportionalLayout: options.proportionalLayout, + proportionalLayout: options.proportionalLayout ?? true, orientation: options.orientation, - styles: options.styles, + styles: options.hideBorders + ? { separatorBorder: 'transparent' } + : undefined, disableAutoResizing: options.disableAutoResizing, className: options.className, }); @@ -139,13 +140,6 @@ export class GridviewComponent this._onDidActiveGroupChange.fire(event); }) ); - - if (!this.options.components) { - this.options.components = {}; - } - if (!this.options.frameworkComponents) { - this.options.frameworkComponents = {}; - } } override updateOptions(options: Partial): void { @@ -216,19 +210,11 @@ export class GridviewComponent this.gridview.deserialize(grid, { fromJSON: (node) => { const { data } = node; - const view = createComponent( - data.id, - data.component, - this.options.components ?? {}, - this.options.frameworkComponents ?? {}, - this.options.frameworkComponentFactory - ? { - createComponent: - this.options.frameworkComponentFactory - .createComponent, - } - : undefined - ); + + const view = this.options.createComponent({ + id: data.id, + name: data.component, + }); queue.push(() => view.init({ @@ -363,19 +349,10 @@ export class GridviewComponent } } - const view = createComponent( - options.id, - options.component, - this.options.components ?? {}, - this.options.frameworkComponents ?? {}, - this.options.frameworkComponentFactory - ? { - createComponent: - this.options.frameworkComponentFactory - .createComponent, - } - : undefined - ); + const view = this.options.createComponent({ + id: options.id, + name: options.component, + }); view.init({ params: options.params ?? {}, diff --git a/packages/dockview-core/src/gridview/options.ts b/packages/dockview-core/src/gridview/options.ts index 8a5025846..ba96378bc 100644 --- a/packages/dockview-core/src/gridview/options.ts +++ b/packages/dockview-core/src/gridview/options.ts @@ -1,21 +1,34 @@ import { GridviewPanel } from './gridviewPanel'; -import { ISplitviewStyles, Orientation } from '../splitview/splitview'; -import { - ComponentConstructor, - FrameworkFactory, -} from '../panel/componentFactory'; +import { Orientation } from '../splitview/splitview'; +import { CreateComponentOptions } from '../dockview/options'; -export interface GridviewComponentOptions { +export interface GridviewOptions { disableAutoResizing?: boolean; - proportionalLayout: boolean; + proportionalLayout?: boolean; orientation: Orientation; - components?: { - [componentName: string]: ComponentConstructor; - }; - frameworkComponents?: { - [componentName: string]: any; - }; - frameworkComponentFactory?: FrameworkFactory; - styles?: ISplitviewStyles; className?: string; + hideBorders?: boolean; } + +export interface GridviewFrameworkOptions { + createComponent: (options: CreateComponentOptions) => GridviewPanel; +} + +export type GridviewComponentOptions = GridviewOptions & + GridviewFrameworkOptions; + +export const PROPERTY_KEYS_GRIDVIEW: (keyof GridviewOptions)[] = (() => { + /** + * by readong the keys from an empty value object TypeScript will error + * when we add or remove new properties to `DockviewOptions` + */ + const properties: Record = { + disableAutoResizing: undefined, + proportionalLayout: undefined, + orientation: undefined, + hideBorders: undefined, + className: undefined, + }; + + return Object.keys(properties) as (keyof GridviewOptions)[]; +})(); diff --git a/packages/dockview-core/src/index.ts b/packages/dockview-core/src/index.ts index 59d4a19e6..dfaec589f 100644 --- a/packages/dockview-core/src/index.ts +++ b/packages/dockview-core/src/index.ts @@ -24,11 +24,19 @@ export * from './splitview/splitview'; export { SplitviewComponentOptions, PanelViewInitParameters, + SplitviewOptions, + SplitviewFrameworkOptions, + PROPERTY_KEYS_SPLITVIEW, } from './splitview/options'; export * from './paneview/paneview'; export * from './gridview/gridview'; -export { GridviewComponentOptions } from './gridview/options'; +export { + GridviewComponentOptions, + GridviewOptions, + GridviewFrameworkOptions, + PROPERTY_KEYS_GRIDVIEW, +} from './gridview/options'; export * from './gridview/baseComponentGridview'; export { @@ -67,7 +75,12 @@ export * from './dockview/dockviewComponent'; export * from './gridview/gridviewComponent'; export * from './splitview/splitviewComponent'; export * from './paneview/paneviewComponent'; -export { PaneviewComponentOptions } from './paneview/options'; +export { + PaneviewComponentOptions, + PaneviewOptions, + PaneviewFrameworkOptions, + PROPERTY_KEYS_PANEVIEW, +} from './paneview/options'; export * from './gridview/gridviewPanel'; export { SplitviewPanel, ISplitviewPanel } from './splitview/splitviewPanel'; diff --git a/packages/dockview-core/src/panel/componentFactory.ts b/packages/dockview-core/src/panel/componentFactory.ts deleted file mode 100644 index fb77f9944..000000000 --- a/packages/dockview-core/src/panel/componentFactory.ts +++ /dev/null @@ -1,58 +0,0 @@ -export interface FrameworkFactory { - createComponent: (id: string, componentId: string, component: any) => T; -} - -export type ComponentConstructor = { - new (id: string, component: string): T; -}; - -export function createComponent( - id: string, - componentName?: string, - components: { - [componentName: string]: ComponentConstructor; - } = {}, - frameworkComponents: { - [componentName: string]: any; - } = {}, - createFrameworkComponent?: FrameworkFactory, - fallback?: () => T -): T { - const Component = - typeof componentName === 'string' - ? components[componentName] - : undefined; - const FrameworkComponent = - typeof componentName === 'string' - ? frameworkComponents[componentName] - : undefined; - - if (Component && FrameworkComponent) { - throw new Error( - `Cannot create '${id}'. component '${componentName}' registered as both a component and frameworkComponent` - ); - } - if (FrameworkComponent) { - if (!createFrameworkComponent) { - throw new Error( - `Cannot create '${id}' for framework component '${componentName}'. you must register a frameworkPanelWrapper to use framework components` - ); - } - return createFrameworkComponent.createComponent( - id, - componentName!, - FrameworkComponent - ); - } - - if (!Component) { - if (fallback) { - return fallback(); - } - throw new Error( - `Cannot create '${id}', no component '${componentName}' provided` - ); - } - - return new Component(id, componentName!); -} diff --git a/packages/dockview-core/src/paneview/defaultPaneviewHeader.ts b/packages/dockview-core/src/paneview/defaultPaneviewHeader.ts index ee37cc597..c53121154 100644 --- a/packages/dockview-core/src/paneview/defaultPaneviewHeader.ts +++ b/packages/dockview-core/src/paneview/defaultPaneviewHeader.ts @@ -2,14 +2,11 @@ import { addDisposableListener } from '../events'; import { PaneviewPanelApiImpl } from '../api/paneviewPanelApi'; import { CompositeDisposable, MutableDisposable } from '../lifecycle'; import { PanelUpdateEvent } from '../panel/types'; -import { IPaneHeaderPart, PanePanelInitParameter } from './paneviewPanel'; +import { IPanePart, PanePanelInitParameter } from './paneviewPanel'; import { toggleClass } from '../dom'; import { createChevronRightButton, createExpandMoreButton } from '../svg'; -export class DefaultHeader - extends CompositeDisposable - implements IPaneHeaderPart -{ +export class DefaultHeader extends CompositeDisposable implements IPanePart { private readonly _expandedIcon = createExpandMoreButton(); private readonly _collapsedIcon = createChevronRightButton(); private readonly disposable = new MutableDisposable(); diff --git a/packages/dockview-core/src/paneview/options.ts b/packages/dockview-core/src/paneview/options.ts index 8aef3e649..f7702638a 100644 --- a/packages/dockview-core/src/paneview/options.ts +++ b/packages/dockview-core/src/paneview/options.ts @@ -1,29 +1,35 @@ -import { - ComponentConstructor, - FrameworkFactory, -} from '../panel/componentFactory'; +import { CreateComponentOptions } from '../dockview/options'; import { PaneviewDndOverlayEvent } from './paneviewComponent'; -import { IPaneBodyPart, IPaneHeaderPart, PaneviewPanel } from './paneviewPanel'; +import { IPanePart } from './paneviewPanel'; -export interface PaneviewComponentOptions { +export interface PaneviewOptions { disableAutoResizing?: boolean; - components?: { - [componentName: string]: ComponentConstructor; - }; - frameworkComponents?: { - [componentName: string]: any; - }; - headerComponents?: { - [componentName: string]: ComponentConstructor; - }; - headerframeworkComponents?: { - [componentName: string]: any; - }; - frameworkWrapper?: { - header: FrameworkFactory; - body: FrameworkFactory; - }; disableDnd?: boolean; showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean; className?: string; } + +export interface PaneviewFrameworkOptions { + createComponent: (options: CreateComponentOptions) => IPanePart; + createHeaderComponent?: ( + options: CreateComponentOptions + ) => IPanePart | undefined; +} + +export type PaneviewComponentOptions = PaneviewOptions & + PaneviewFrameworkOptions; + +export const PROPERTY_KEYS_PANEVIEW: (keyof PaneviewOptions)[] = (() => { + /** + * by readong the keys from an empty value object TypeScript will error + * when we add or remove new properties to `DockviewOptions` + */ + const properties: Record = { + disableAutoResizing: undefined, + disableDnd: undefined, + showDndOverlay: undefined, + className: undefined, + }; + + return Object.keys(properties) as (keyof PaneviewOptions)[]; +})(); diff --git a/packages/dockview-core/src/paneview/paneviewComponent.ts b/packages/dockview-core/src/paneview/paneviewComponent.ts index 171f4a703..651f5f406 100644 --- a/packages/dockview-core/src/paneview/paneviewComponent.ts +++ b/packages/dockview-core/src/paneview/paneviewComponent.ts @@ -1,5 +1,4 @@ import { PaneviewApi } from '../api/component.api'; -import { createComponent } from '../panel/componentFactory'; import { Emitter, Event } from '../events'; import { CompositeDisposable, @@ -9,12 +8,7 @@ import { import { LayoutPriority, Orientation, Sizing } from '../splitview/splitview'; import { PaneviewComponentOptions } from './options'; import { Paneview } from './paneview'; -import { - IPaneBodyPart, - IPaneHeaderPart, - PaneviewPanel, - IPaneviewPanel, -} from './paneviewPanel'; +import { IPanePart, PaneviewPanel, IPaneviewPanel } from './paneviewPanel'; import { DraggablePaneviewPanel, PaneviewDropEvent, @@ -61,8 +55,8 @@ export class PaneFramework extends DraggablePaneviewPanel { id: string; component: string; headerComponent: string | undefined; - body: IPaneBodyPart; - header: IPaneHeaderPart; + body: IPanePart; + header: IPanePart; orientation: Orientation; isExpanded: boolean; disableDnd: boolean; @@ -218,13 +212,6 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { this._options = options; - if (!options.components) { - options.components = {}; - } - if (!options.frameworkComponents) { - options.frameworkComponents = {}; - } - this.paneview = new Paneview(this.element, { // only allow paneview in the vertical orientation for now orientation: Orientation.VERTICAL, @@ -257,36 +244,21 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { addPanel( options: AddPaneviewComponentOptions ): IPaneviewPanel { - const body = createComponent( - options.id, - options.component, - this.options.components ?? {}, - this.options.frameworkComponents ?? {}, - this.options.frameworkWrapper - ? { - createComponent: - this.options.frameworkWrapper.body.createComponent, - } - : undefined - ); + const body = this.options.createComponent({ + id: options.id, + name: options.component, + }); - let header: IPaneHeaderPart; + let header: IPanePart | undefined; - if (options.headerComponent) { - header = createComponent( - options.id, - options.headerComponent, - this.options.headerComponents ?? {}, - this.options.headerframeworkComponents, - this.options.frameworkWrapper - ? { - createComponent: - this.options.frameworkWrapper.header - .createComponent, - } - : undefined - ); - } else { + if (options.headerComponent && this.options.createHeaderComponent) { + header = this.options.createHeaderComponent({ + id: options.id, + name: options.headerComponent, + }); + } + + if (!header) { header = new DefaultHeader(); } @@ -395,37 +367,24 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { views: views.map((view) => { const data = view.data; - const body = createComponent( - data.id, - data.component, - this.options.components ?? {}, - this.options.frameworkComponents ?? {}, - this.options.frameworkWrapper - ? { - createComponent: - this.options.frameworkWrapper.body - .createComponent, - } - : undefined - ); + const body = this.options.createComponent({ + id: data.id, + name: data.component, + }); - let header: IPaneHeaderPart; + let header: IPanePart | undefined; - if (data.headerComponent) { - header = createComponent( - data.id, - data.headerComponent, - this.options.headerComponents ?? {}, - this.options.headerframeworkComponents ?? {}, - this.options.frameworkWrapper - ? { - createComponent: - this.options.frameworkWrapper.header - .createComponent, - } - : undefined - ); - } else { + if ( + data.headerComponent && + this.options.createHeaderComponent + ) { + header = this.options.createHeaderComponent({ + id: data.id, + name: data.headerComponent, + }); + } + + if (!header) { header = new DefaultHeader(); } diff --git a/packages/dockview-core/src/paneview/paneviewPanel.ts b/packages/dockview-core/src/paneview/paneviewPanel.ts index 38b4586f6..02710da90 100644 --- a/packages/dockview-core/src/paneview/paneviewPanel.ts +++ b/packages/dockview-core/src/paneview/paneviewPanel.ts @@ -36,13 +36,7 @@ export interface PanePanelComponentInitParameter api: PaneviewPanelApiImpl; } -export interface IPaneBodyPart extends IDisposable { - readonly element: HTMLElement; - update(params: PanelUpdateEvent): void; - init(parameters: PanePanelComponentInitParameter): void; -} - -export interface IPaneHeaderPart extends IDisposable { +export interface IPanePart extends IDisposable { readonly element: HTMLElement; update(params: PanelUpdateEvent): void; init(parameters: PanePanelComponentInitParameter): void; @@ -85,8 +79,8 @@ export abstract class PaneviewPanel private _isExpanded = false; protected header?: HTMLElement; protected body?: HTMLElement; - private bodyPart?: IPaneHeaderPart; - private headerPart?: IPaneBodyPart; + private bodyPart?: IPanePart; + private headerPart?: IPanePart; private expandedSize = 0; private animationTimer: any; private _orientation: Orientation; @@ -338,6 +332,6 @@ export abstract class PaneviewPanel }; } - protected abstract getBodyComponent(): IPaneBodyPart; - protected abstract getHeaderComponent(): IPaneHeaderPart; + protected abstract getBodyComponent(): IPanePart; + protected abstract getHeaderComponent(): IPanePart; } diff --git a/packages/dockview-core/src/splitview/options.ts b/packages/dockview-core/src/splitview/options.ts index 01c32a040..05955f279 100644 --- a/packages/dockview-core/src/splitview/options.ts +++ b/packages/dockview-core/src/splitview/options.ts @@ -2,10 +2,7 @@ import { PanelInitParameters } from '../panel/types'; import { SplitViewOptions, LayoutPriority } from './splitview'; import { SplitviewPanel } from './splitviewPanel'; import { SplitviewComponent } from './splitviewComponent'; -import { - ComponentConstructor, - FrameworkFactory, -} from '../panel/componentFactory'; +import { CreateComponentOptions } from '../dockview/options'; export interface PanelViewInitParameters extends PanelInitParameters { minimumSize?: number; @@ -15,14 +12,32 @@ export interface PanelViewInitParameters extends PanelInitParameters { accessor: SplitviewComponent; } -export interface SplitviewComponentOptions extends SplitViewOptions { +export interface SplitviewOptions extends SplitViewOptions { disableAutoResizing?: boolean; - components?: { - [componentName: string]: ComponentConstructor; - }; - frameworkComponents?: { - [componentName: string]: any; - }; - frameworkWrapper?: FrameworkFactory; className?: string; } + +export interface SplitviewFrameworkOptions { + createComponent: (options: CreateComponentOptions) => SplitviewPanel; +} + +export type SplitviewComponentOptions = SplitviewOptions & + SplitviewFrameworkOptions; + +export const PROPERTY_KEYS_SPLITVIEW: (keyof SplitviewOptions)[] = (() => { + /** + * by readong the keys from an empty value object TypeScript will error + * when we add or remove new properties to `DockviewOptions` + */ + const properties: Record = { + orientation: undefined, + descriptor: undefined, + proportionalLayout: undefined, + styles: undefined, + margin: undefined, + disableAutoResizing: undefined, + className: undefined, + }; + + return Object.keys(properties) as (keyof SplitviewOptions)[]; +})(); diff --git a/packages/dockview-core/src/splitview/splitview.ts b/packages/dockview-core/src/splitview/splitview.ts index c4ceeb3d0..fc5857d08 100644 --- a/packages/dockview-core/src/splitview/splitview.ts +++ b/packages/dockview-core/src/splitview/splitview.ts @@ -32,11 +32,11 @@ export interface ISplitviewStyles { } export interface SplitViewOptions { - readonly orientation: Orientation; - readonly descriptor?: ISplitViewDescriptor; - readonly proportionalLayout?: boolean; - readonly styles?: ISplitviewStyles; - readonly margin?: number; + orientation: Orientation; + descriptor?: ISplitViewDescriptor; + proportionalLayout?: boolean; + styles?: ISplitviewStyles; + margin?: number; } export enum LayoutPriority { diff --git a/packages/dockview-core/src/splitview/splitviewComponent.ts b/packages/dockview-core/src/splitview/splitviewComponent.ts index ab8bb6cf5..5e988c422 100644 --- a/packages/dockview-core/src/splitview/splitviewComponent.ts +++ b/packages/dockview-core/src/splitview/splitviewComponent.ts @@ -15,7 +15,6 @@ import { SplitviewComponentOptions } from './options'; import { BaseComponentOptions, Parameters } from '../panel/types'; import { Emitter, Event } from '../events'; import { SplitviewPanel, ISplitviewPanel } from './splitviewPanel'; -import { createComponent } from '../panel/componentFactory'; import { Resizable } from '../resizable'; import { Classnames } from '../dom'; @@ -167,13 +166,6 @@ export class SplitviewComponent this._options = options; - if (!options.components) { - options.components = {}; - } - if (!options.frameworkComponents) { - options.frameworkComponents = {}; - } - this.splitview = new Splitview(this.element, options); this.addDisposables( @@ -267,18 +259,10 @@ export class SplitviewComponent throw new Error(`panel ${options.id} already exists`); } - const view = createComponent( - options.id, - options.component, - this.options.components ?? {}, - this.options.frameworkComponents ?? {}, - this.options.frameworkWrapper - ? { - createComponent: - this.options.frameworkWrapper.createComponent, - } - : undefined - ); + const view = this.options.createComponent({ + id: options.id, + name: options.component, + }); view.orientation = this.splitview.orientation; @@ -367,19 +351,10 @@ export class SplitviewComponent throw new Error(`panel ${data.id} already exists`); } - const panel = createComponent( - data.id, - data.component, - this.options.components ?? {}, - this.options.frameworkComponents ?? {}, - this.options.frameworkWrapper - ? { - createComponent: - this.options.frameworkWrapper - .createComponent, - } - : undefined - ); + const panel = this.options.createComponent({ + id: data.id, + name: data.component, + }); queue.push(() => { panel.init({ diff --git a/packages/dockview-vue/src/dockview/dockview.vue b/packages/dockview-vue/src/dockview/dockview.vue index 6442ad5eb..35508d49a 100644 --- a/packages/dockview-vue/src/dockview/dockview.vue +++ b/packages/dockview-vue/src/dockview/dockview.vue @@ -2,7 +2,7 @@ import { DockviewApi, type DockviewOptions, - PROPERTY_KEYS, + PROPERTY_KEYS_DOCKVIEW, type DockviewFrameworkOptions, createDockview, } from 'dockview-core'; @@ -25,7 +25,7 @@ import { import type { IDockviewVueProps, VueEvents } from './types'; function extractCoreOptions(props: IDockviewVueProps): DockviewOptions { - const coreOptions = (PROPERTY_KEYS as (keyof DockviewOptions)[]).reduce( + const coreOptions = (PROPERTY_KEYS_DOCKVIEW as (keyof DockviewOptions)[]).reduce( (obj, key) => { (obj as any)[key] = props[key]; return obj; @@ -43,7 +43,7 @@ const props = defineProps(); const el = ref(null); const instance = ref(null); -PROPERTY_KEYS.forEach((coreOptionKey) => { +PROPERTY_KEYS_DOCKVIEW.forEach((coreOptionKey) => { watch( () => props[coreOptionKey], (newValue, oldValue) => { diff --git a/packages/dockview/src/dockview/dockview.tsx b/packages/dockview/src/dockview/dockview.tsx index 550c632b9..fefc605f1 100644 --- a/packages/dockview/src/dockview/dockview.tsx +++ b/packages/dockview/src/dockview/dockview.tsx @@ -10,7 +10,7 @@ import { IDockviewPanelHeaderProps, IDockviewPanelProps, DockviewOptions, - PROPERTY_KEYS, + PROPERTY_KEYS_DOCKVIEW, DockviewComponentOptions, DockviewFrameworkOptions, DockviewReadyEvent, @@ -40,7 +40,6 @@ function createGroupControlElement( const DEFAULT_REACT_TAB = 'props.defaultTabComponent'; export interface IDockviewReactProps extends DockviewOptions { - className?: string; tabComponents?: Record< string, React.FunctionComponent @@ -58,7 +57,7 @@ export interface IDockviewReactProps extends DockviewOptions { } function extractCoreOptions(props: IDockviewReactProps): DockviewOptions { - const coreOptions = PROPERTY_KEYS.reduce((obj, key) => { + const coreOptions = PROPERTY_KEYS_DOCKVIEW.reduce((obj, key) => { if (key in props) { obj[key] = props[key] as any; } @@ -82,7 +81,7 @@ export const DockviewReact = React.forwardRef( () => { const changes: Partial = {}; - PROPERTY_KEYS.forEach((propKey) => { + PROPERTY_KEYS_DOCKVIEW.forEach((propKey) => { const key = propKey; const propValue = props[key]; @@ -99,7 +98,7 @@ export const DockviewReact = React.forwardRef( prevProps.current = props; }, - PROPERTY_KEYS.map((key) => props[key]) + PROPERTY_KEYS_DOCKVIEW.map((key) => props[key]) ); React.useEffect(() => { diff --git a/packages/dockview/src/gridview/gridview.tsx b/packages/dockview/src/gridview/gridview.tsx index c63307593..60b172651 100644 --- a/packages/dockview/src/gridview/gridview.tsx +++ b/packages/dockview/src/gridview/gridview.tsx @@ -1,13 +1,17 @@ import React from 'react'; import { GridviewPanelApi, - Orientation, GridviewApi, createGridview, + GridviewOptions, + PROPERTY_KEYS_GRIDVIEW, + GridviewComponentOptions, + GridviewFrameworkOptions, } from 'dockview-core'; import { ReactGridPanelView } from './view'; import { usePortalsLifecycle } from '../react'; import { PanelParameters } from '../types'; + export interface GridviewReadyEvent { api: GridviewApi; } @@ -18,14 +22,20 @@ export interface IGridviewPanelProps containerApi: GridviewApi; } -export interface IGridviewReactProps { - orientation?: Orientation; +export interface IGridviewReactProps extends GridviewOptions { onReady: (event: GridviewReadyEvent) => void; components: Record>; - hideBorders?: boolean; - className?: string; - proportionalLayout?: boolean; - disableAutoResizing?: boolean; +} + +function extractCoreOptions(props: IGridviewReactProps): GridviewOptions { + const coreOptions = PROPERTY_KEYS_GRIDVIEW.reduce((obj, key) => { + if (key in props) { + obj[key] = props[key] as any; + } + return obj; + }, {} as Partial); + + return coreOptions as GridviewOptions; } export const GridviewReact = React.forwardRef( @@ -36,6 +46,32 @@ export const GridviewReact = React.forwardRef( React.useImperativeHandle(ref, () => domRef.current!, []); + const prevProps = React.useRef>({}); + + React.useEffect( + () => { + const changes: Partial = {}; + + PROPERTY_KEYS_GRIDVIEW.forEach((propKey) => { + const key = propKey; + const propValue = props[key]; + + if (key in props && propValue !== prevProps.current[key]) { + changes[key] = propValue as any; + } + }); + + if (gridviewRef.current) { + gridviewRef.current.updateOptions(changes); + } else { + // not yet fully initialized + } + + prevProps.current = props; + }, + PROPERTY_KEYS_GRIDVIEW.map((key) => props[key]) + ); + React.useEffect(() => { if (!domRef.current) { return () => { @@ -43,29 +79,20 @@ export const GridviewReact = React.forwardRef( }; } - const api = createGridview(domRef.current, { - disableAutoResizing: props.disableAutoResizing, - proportionalLayout: - typeof props.proportionalLayout === 'boolean' - ? props.proportionalLayout - : true, - orientation: props.orientation ?? Orientation.HORIZONTAL, - frameworkComponents: props.components, - frameworkComponentFactory: { - createComponent: (id: string, componentId, component) => { - return new ReactGridPanelView( - id, - componentId, - component, - { - addPortal, - } - ); - }, + const frameworkOptions: GridviewFrameworkOptions = { + createComponent: (options) => { + return new ReactGridPanelView( + options.id, + options.name, + props.components[options.name], + { addPortal } + ); }, - styles: props.hideBorders - ? { separatorBorder: 'transparent' } - : undefined, + }; + + const api = createGridview(domRef.current, { + ...extractCoreOptions(props), + ...frameworkOptions, }); const { clientWidth, clientHeight } = domRef.current; @@ -87,7 +114,14 @@ export const GridviewReact = React.forwardRef( return; } gridviewRef.current.updateOptions({ - frameworkComponents: props.components, + createComponent: (options) => { + return new ReactGridPanelView( + options.id, + options.name, + props.components[options.name], + { addPortal } + ); + }, }); }, [props.components]); diff --git a/packages/dockview/src/paneview/paneview.tsx b/packages/dockview/src/paneview/paneview.tsx index b5db59540..2dc623638 100644 --- a/packages/dockview/src/paneview/paneview.tsx +++ b/packages/dockview/src/paneview/paneview.tsx @@ -5,6 +5,10 @@ import { PaneviewApi, PaneviewDropEvent, createPaneview, + PaneviewOptions, + PROPERTY_KEYS_PANEVIEW, + PaneviewComponentOptions, + PaneviewFrameworkOptions, } from 'dockview-core'; import { usePortalsLifecycle } from '../react'; import { PanePanelSection } from './view'; @@ -21,20 +25,28 @@ export interface IPaneviewPanelProps title: string; } -export interface IPaneviewReactProps { +export interface IPaneviewReactProps extends PaneviewOptions { onReady: (event: PaneviewReadyEvent) => void; components: Record>; headerComponents?: Record< string, React.FunctionComponent >; - className?: string; - disableAutoResizing?: boolean; - disableDnd?: boolean; showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean; onDidDrop?(event: PaneviewDropEvent): void; } +function extractCoreOptions(props: IPaneviewReactProps): PaneviewOptions { + const coreOptions = PROPERTY_KEYS_PANEVIEW.reduce((obj, key) => { + if (key in props) { + obj[key] = props[key] as any; + } + return obj; + }, {} as Partial); + + return coreOptions as PaneviewOptions; +} + export const PaneviewReact = React.forwardRef( (props: IPaneviewReactProps, ref: React.ForwardedRef) => { const domRef = React.useRef(null); @@ -43,35 +55,64 @@ export const PaneviewReact = React.forwardRef( React.useImperativeHandle(ref, () => domRef.current!, []); - React.useEffect(() => { - const createComponent = ( - id: string, - _componentId: string, - component: any - ) => - new PanePanelSection(id, component, { - addPortal, + const prevProps = React.useRef>({}); + + React.useEffect( + () => { + const changes: Partial = {}; + + PROPERTY_KEYS_PANEVIEW.forEach((propKey) => { + const key = propKey; + const propValue = props[key]; + + if (key in props && propValue !== prevProps.current[key]) { + changes[key] = propValue as any; + } }); - const api = createPaneview(domRef.current!, { - disableAutoResizing: props.disableAutoResizing, - frameworkComponents: props.components, - components: {}, - headerComponents: {}, - disableDnd: props.disableDnd, - headerframeworkComponents: props.headerComponents, - frameworkWrapper: { - header: { - createComponent, - }, - body: { - createComponent, - }, + if (paneviewRef.current) { + paneviewRef.current.updateOptions(changes); + } else { + // not yet fully initialized + } + + prevProps.current = props; + }, + PROPERTY_KEYS_PANEVIEW.map((key) => props[key]) + ); + + React.useEffect(() => { + if (!domRef.current) { + return () => { + // noop + }; + } + + const headerComponents = props.headerComponents ?? {}; + + const frameworkOptions: PaneviewFrameworkOptions = { + createComponent: (options) => { + return new PanePanelSection( + options.id, + props.components[options.name], + { addPortal } + ); }, - showDndOverlay: props.showDndOverlay, + createHeaderComponent: (options) => { + return new PanePanelSection( + options.id, + headerComponents[options.name], + { addPortal } + ); + }, + }; + + const api = createPaneview(domRef.current, { + ...extractCoreOptions(props), + ...frameworkOptions, }); - const { clientWidth, clientHeight } = domRef.current!; + const { clientWidth, clientHeight } = domRef.current; api.layout(clientWidth, clientHeight); if (props.onReady) { @@ -90,7 +131,13 @@ export const PaneviewReact = React.forwardRef( return; } paneviewRef.current.updateOptions({ - frameworkComponents: props.components, + createComponent: (options) => { + return new PanePanelSection( + options.id, + props.components[options.name], + { addPortal } + ); + }, }); }, [props.components]); @@ -98,26 +145,30 @@ export const PaneviewReact = React.forwardRef( if (!paneviewRef.current) { return; } + + const headerComponents = props.headerComponents ?? {}; + paneviewRef.current.updateOptions({ - headerframeworkComponents: props.headerComponents, + createHeaderComponent: (options) => { + return new PanePanelSection( + options.id, + headerComponents[options.name], + { addPortal } + ); + }, }); }, [props.headerComponents]); React.useEffect(() => { if (!paneviewRef.current) { return () => { - // + // noop }; } - const api = paneviewRef.current; - - const disposable = api.onDidDrop((event) => { + const disposable = paneviewRef.current.onDidDrop((event) => { if (props.onDidDrop) { - props.onDidDrop({ - ...event, - api, - }); + props.onDidDrop(event); } }); diff --git a/packages/dockview/src/paneview/view.ts b/packages/dockview/src/paneview/view.ts index 3d3a5d8f6..6840e49d4 100644 --- a/packages/dockview/src/paneview/view.ts +++ b/packages/dockview/src/paneview/view.ts @@ -1,13 +1,13 @@ import React from 'react'; import { PanelUpdateEvent, - IPaneBodyPart, + IPanePart, PanePanelComponentInitParameter, } from 'dockview-core'; import { ReactPart, ReactPortalStore } from '../react'; import { IPaneviewPanelProps } from './paneview'; -export class PanePanelSection implements IPaneBodyPart { +export class PanePanelSection implements IPanePart { private readonly _element: HTMLElement; private part?: ReactPart; diff --git a/packages/dockview/src/splitview/splitview.tsx b/packages/dockview/src/splitview/splitview.tsx index 6c516ce8c..63d2cf5d9 100644 --- a/packages/dockview/src/splitview/splitview.tsx +++ b/packages/dockview/src/splitview/splitview.tsx @@ -2,8 +2,11 @@ import React from 'react'; import { SplitviewApi, SplitviewPanelApi, - Orientation, createSplitview, + SplitviewOptions, + PROPERTY_KEYS_SPLITVIEW, + SplitviewFrameworkOptions, + SplitviewComponentOptions, } from 'dockview-core'; import { usePortalsLifecycle } from '../react'; import { PanelParameters } from '../types'; @@ -19,14 +22,20 @@ export interface ISplitviewPanelProps containerApi: SplitviewApi; } -export interface ISplitviewReactProps { - orientation?: Orientation; +export interface ISplitviewReactProps extends SplitviewOptions { onReady: (event: SplitviewReadyEvent) => void; components: Record>; - proportionalLayout?: boolean; - hideBorders?: boolean; - className?: string; - disableAutoResizing?: boolean; +} + +function extractCoreOptions(props: ISplitviewReactProps): SplitviewOptions { + const coreOptions = PROPERTY_KEYS_SPLITVIEW.reduce((obj, key) => { + if (key in props) { + obj[key] = props[key] as any; + } + return obj; + }, {} as Partial); + + return coreOptions as SplitviewOptions; } export const SplitviewReact = React.forwardRef( @@ -37,32 +46,56 @@ export const SplitviewReact = React.forwardRef( React.useImperativeHandle(ref, () => domRef.current!, []); + const prevProps = React.useRef>({}); + + React.useEffect( + () => { + const changes: Partial = {}; + + PROPERTY_KEYS_SPLITVIEW.forEach((propKey) => { + const key = propKey; + const propValue = props[key]; + + if (key in props && propValue !== prevProps.current[key]) { + changes[key] = propValue as any; + } + }); + + if (splitviewRef.current) { + splitviewRef.current.updateOptions(changes); + } else { + // not yet fully initialized + } + + prevProps.current = props; + }, + PROPERTY_KEYS_SPLITVIEW.map((key) => props[key]) + ); + React.useEffect(() => { - const api = createSplitview(domRef.current!, { - disableAutoResizing: props.disableAutoResizing, - orientation: props.orientation ?? Orientation.HORIZONTAL, - frameworkComponents: props.components, - frameworkWrapper: { - createComponent: ( - id: string, - componentId, - component: any - ) => { - return new ReactPanelView(id, componentId, component, { - addPortal, - }); - }, + if (!domRef.current) { + return () => { + // noop + }; + } + + const frameworkOptions: SplitviewFrameworkOptions = { + createComponent: (options) => { + return new ReactPanelView( + options.id, + options.name, + props.components[options.name], + { addPortal } + ); }, - proportionalLayout: - typeof props.proportionalLayout === 'boolean' - ? props.proportionalLayout - : true, - styles: props.hideBorders - ? { separatorBorder: 'transparent' } - : undefined, + }; + + const api = createSplitview(domRef.current, { + ...extractCoreOptions(props), + ...frameworkOptions, }); - const { clientWidth, clientHeight } = domRef.current!; + const { clientWidth, clientHeight } = domRef.current; api.layout(clientWidth, clientHeight); if (props.onReady) { @@ -81,7 +114,14 @@ export const SplitviewReact = React.forwardRef( return; } splitviewRef.current.updateOptions({ - frameworkComponents: props.components, + createComponent: (options) => { + return new ReactPanelView( + options.id, + options.name, + props.components[options.name], + { addPortal } + ); + }, }); }, [props.components]); From a53665adc5c19ab989e4345f3056c37e98102853 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 23 Dec 2024 20:04:14 +0000 Subject: [PATCH 29/83] chore: autogen docs --- packages/docs/src/generated/api.output.json | 1203 ++++++++----------- 1 file changed, 476 insertions(+), 727 deletions(-) diff --git a/packages/docs/src/generated/api.output.json b/packages/docs/src/generated/api.output.json index 2e827390f..85cf823f6 100644 --- a/packages/docs/src/generated/api.output.json +++ b/packages/docs/src/generated/api.output.json @@ -2841,7 +2841,7 @@ }, { "name": "addFloatingGroup", - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: FloatingGroupOptions): void", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: FloatingGroupOptions): void", "kind": "method", "signature": [ { @@ -2858,18 +2858,18 @@ "parameters": [ { "name": "item", - "code": "item: IDockviewPanel | DockviewGroupPanel", + "code": "item: DockviewGroupPanel | IDockviewPanel", "type": { "type": "or", "values": [ { "type": "reference", - "value": "IDockviewPanel", + "value": "DockviewGroupPanel", "source": "dockview-core" }, { "type": "reference", - "value": "DockviewGroupPanel", + "value": "IDockviewPanel", "source": "dockview-core" } ] @@ -2891,7 +2891,7 @@ "type": "intrinsic", "value": "void" }, - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: FloatingGroupOptions): void", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: FloatingGroupOptions): void", "kind": "callSignature" } ], @@ -3060,7 +3060,7 @@ }, { "name": "addPopoutGroup", - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", "kind": "method", "signature": [ { @@ -3077,18 +3077,18 @@ "parameters": [ { "name": "item", - "code": "item: IDockviewPanel | DockviewGroupPanel", + "code": "item: DockviewGroupPanel | IDockviewPanel", "type": { "type": "or", "values": [ { "type": "reference", - "value": "IDockviewPanel", + "value": "DockviewGroupPanel", "source": "dockview-core" }, { "type": "reference", - "value": "DockviewGroupPanel", + "value": "IDockviewPanel", "source": "dockview-core" } ] @@ -3279,7 +3279,7 @@ } ] }, - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", "kind": "callSignature" } ], @@ -10187,7 +10187,7 @@ }, { "name": "getBodyComponent", - "code": "(): IPaneBodyPart", + "code": "(): IPanePart", "kind": "method", "signature": [ { @@ -10196,10 +10196,10 @@ "parameters": [], "returnType": { "type": "reference", - "value": "IPaneBodyPart", + "value": "IPanePart", "source": "dockview-core" }, - "code": "(): IPaneBodyPart", + "code": "(): IPanePart", "kind": "callSignature" } ] @@ -10225,7 +10225,7 @@ }, { "name": "getHeaderComponent", - "code": "(): IPaneHeaderPart", + "code": "(): IPanePart", "kind": "method", "signature": [ { @@ -10234,10 +10234,10 @@ "parameters": [], "returnType": { "type": "reference", - "value": "IPaneHeaderPart", + "value": "IPanePart", "source": "dockview-core" }, - "code": "(): IPaneHeaderPart", + "code": "(): IPanePart", "kind": "callSignature" } ] @@ -15393,7 +15393,7 @@ }, { "name": "getBodyComponent", - "code": "(): IPaneBodyPart", + "code": "(): IPanePart", "kind": "method", "signature": [ { @@ -15402,10 +15402,10 @@ "parameters": [], "returnType": { "type": "reference", - "value": "IPaneBodyPart", + "value": "IPanePart", "source": "dockview-core" }, - "code": "(): IPaneBodyPart", + "code": "(): IPanePart", "kind": "callSignature" } ] @@ -15431,7 +15431,7 @@ }, { "name": "getHeaderComponent", - "code": "(): IPaneHeaderPart", + "code": "(): IPanePart", "kind": "method", "signature": [ { @@ -15440,10 +15440,10 @@ "parameters": [], "returnType": { "type": "reference", - "value": "IPaneHeaderPart", + "value": "IPanePart", "source": "dockview-core" }, - "code": "(): IPaneHeaderPart", + "code": "(): IPanePart", "kind": "callSignature" } ] @@ -18289,7 +18289,7 @@ }, { "name": "getBodyComponent", - "code": "(): IPaneBodyPart", + "code": "(): IPanePart", "kind": "method", "signature": [ { @@ -18298,10 +18298,10 @@ "parameters": [], "returnType": { "type": "reference", - "value": "IPaneBodyPart", + "value": "IPanePart", "source": "dockview-core" }, - "code": "(): IPaneBodyPart", + "code": "(): IPanePart", "kind": "callSignature" } ] @@ -18327,7 +18327,7 @@ }, { "name": "getHeaderComponent", - "code": "(): IPaneHeaderPart", + "code": "(): IPanePart", "kind": "method", "signature": [ { @@ -18336,10 +18336,10 @@ "parameters": [], "returnType": { "type": "reference", - "value": "IPaneHeaderPart", + "value": "IPanePart", "source": "dockview-core" }, - "code": "(): IPaneHeaderPart", + "code": "(): IPanePart", "kind": "callSignature" } ] @@ -25437,7 +25437,7 @@ "summary": [ { "kind": "text", - "text": "The same-origin path at which the popout window will be created\n\nDefaults to " + "text": "The same-origin path at which the popout window will be created\r\n\r\nDefaults to " }, { "kind": "code", @@ -26087,126 +26087,55 @@ "BasePanelViewState" ] }, - "GridviewComponentOptions": { + "GridviewFrameworkOptions": { "kind": "interface", - "name": "GridviewComponentOptions", + "name": "GridviewFrameworkOptions", "children": [ { - "name": "className", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "components", - "code": "", + "name": "createComponent", + "code": "(options: CreateComponentOptions): GridviewPanel", "kind": "property", "type": { "type": "reflection", "value": { "name": "__type", - "code": "", - "kind": "typeLiteral" - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "disableAutoResizing", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "frameworkComponentFactory", - "code": "FrameworkFactory>", - "kind": "property", - "type": { - "type": "reference", - "value": "FrameworkFactory", - "source": "dockview-core", - "typeArguments": [ - { - "type": "reference", - "value": "GridviewPanel", - "source": "dockview-core", - "typeArguments": [ - { + "code": "(options: CreateComponentOptions): GridviewPanel", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [ + { + "name": "options", + "code": "options: CreateComponentOptions", + "type": { + "type": "reference", + "value": "CreateComponentOptions", + "source": "dockview-core" + }, + "kind": "parameter" + } + ], + "returnType": { "type": "reference", - "value": "GridviewPanelApiImpl", - "source": "dockview-core" - } - ] - } - ] - }, - "flags": { - "isOptional": true - } - }, - { - "name": "frameworkComponents", - "code": "", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "", - "kind": "typeLiteral" + "value": "GridviewPanel", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "GridviewPanelApiImpl", + "source": "dockview-core" + } + ] + }, + "code": "(options: CreateComponentOptions): GridviewPanel", + "kind": "callSignature" + } + ] } }, - "flags": { - "isOptional": true - } - }, - { - "name": "orientation", - "code": "Orientation", - "kind": "property", - "type": { - "type": "reference", - "value": "Orientation", - "source": "dockview-core" - }, "flags": {} - }, - { - "name": "proportionalLayout", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": {} - }, - { - "name": "styles", - "code": "ISplitviewStyles", - "kind": "property", - "type": { - "type": "reference", - "value": "ISplitviewStyles", - "source": "dockview-core" - }, - "flags": { - "isOptional": true - } } ], "extends": [] @@ -26349,6 +26278,72 @@ "PanelInitParameters" ] }, + "GridviewOptions": { + "kind": "interface", + "name": "GridviewOptions", + "children": [ + { + "name": "className", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "disableAutoResizing", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "hideBorders", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "orientation", + "code": "Orientation", + "kind": "property", + "type": { + "type": "reference", + "value": "Orientation", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "proportionalLayout", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isOptional": true + } + } + ], + "extends": [] + }, "GridviewPanelApi": { "kind": "interface", "name": "GridviewPanelApi", @@ -29132,7 +29127,7 @@ }, { "name": "addFloatingGroup", - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: FloatingGroupOptions): void", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: FloatingGroupOptions): void", "kind": "method", "signature": [ { @@ -29141,18 +29136,18 @@ "parameters": [ { "name": "item", - "code": "item: IDockviewPanel | DockviewGroupPanel", + "code": "item: DockviewGroupPanel | IDockviewPanel", "type": { "type": "or", "values": [ { "type": "reference", - "value": "IDockviewPanel", + "value": "DockviewGroupPanel", "source": "dockview-core" }, { "type": "reference", - "value": "DockviewGroupPanel", + "value": "IDockviewPanel", "source": "dockview-core" } ] @@ -29174,7 +29169,7 @@ "type": "intrinsic", "value": "void" }, - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: FloatingGroupOptions): void", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: FloatingGroupOptions): void", "kind": "callSignature" } ] @@ -29303,7 +29298,7 @@ }, { "name": "addPopoutGroup", - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", "kind": "method", "signature": [ { @@ -29312,18 +29307,18 @@ "parameters": [ { "name": "item", - "code": "item: IDockviewPanel | DockviewGroupPanel", + "code": "item: DockviewGroupPanel | IDockviewPanel", "type": { "type": "or", "values": [ { "type": "reference", - "value": "IDockviewPanel", + "value": "DockviewGroupPanel", "source": "dockview-core" }, { "type": "reference", - "value": "DockviewGroupPanel", + "value": "IDockviewPanel", "source": "dockview-core" } ] @@ -29514,7 +29509,7 @@ } ] }, - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", "kind": "callSignature" } ] @@ -34446,114 +34441,9 @@ ], "extends": [] }, - "IPaneBodyPart": { + "IPanePart": { "kind": "interface", - "name": "IPaneBodyPart", - "children": [ - { - "name": "element", - "code": "HTMLElement", - "kind": "property", - "type": { - "type": "reference", - "value": "HTMLElement", - "source": "typescript" - }, - "flags": { - "isReadonly": true - } - }, - { - "name": "dispose", - "code": "(): void", - "kind": "method", - "signature": [ - { - "name": "dispose", - "typeParameters": [], - "parameters": [], - "returnType": { - "type": "intrinsic", - "value": "void" - }, - "code": "(): void", - "kind": "callSignature" - } - ] - }, - { - "name": "init", - "code": "(parameters: PanePanelComponentInitParameter): void", - "kind": "method", - "signature": [ - { - "name": "init", - "typeParameters": [], - "parameters": [ - { - "name": "parameters", - "code": "parameters: PanePanelComponentInitParameter", - "type": { - "type": "reference", - "value": "PanePanelComponentInitParameter", - "source": "dockview-core" - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "void" - }, - "code": "(parameters: PanePanelComponentInitParameter): void", - "kind": "callSignature" - } - ] - }, - { - "name": "update", - "code": "(params: PanelUpdateEvent): void", - "kind": "method", - "signature": [ - { - "name": "update", - "typeParameters": [], - "parameters": [ - { - "name": "params", - "code": "params: PanelUpdateEvent", - "type": { - "type": "reference", - "value": "PanelUpdateEvent", - "source": "dockview-core", - "typeArguments": [ - { - "type": "reference", - "value": "Parameters", - "source": "dockview-core" - } - ] - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "void" - }, - "code": "(params: PanelUpdateEvent): void", - "kind": "callSignature" - } - ] - } - ], - "extends": [ - "IDisposable" - ] - }, - "IPaneHeaderPart": { - "kind": "interface", - "name": "IPaneHeaderPart", + "name": "IPanePart", "children": [ { "name": "element", @@ -38760,207 +38650,6 @@ "PanelInitParameters" ] }, - "PaneviewComponentOptions": { - "kind": "interface", - "name": "PaneviewComponentOptions", - "children": [ - { - "name": "className", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "components", - "code": "", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "", - "kind": "typeLiteral" - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "disableAutoResizing", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "disableDnd", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "frameworkComponents", - "code": "", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "", - "kind": "typeLiteral" - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "frameworkWrapper", - "code": "{ body: FrameworkFactory, header: FrameworkFactory }", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "{ body: FrameworkFactory, header: FrameworkFactory }", - "kind": "typeLiteral", - "properties": [ - { - "name": "body", - "code": "FrameworkFactory", - "kind": "property", - "type": { - "type": "reference", - "value": "FrameworkFactory", - "source": "dockview-core", - "typeArguments": [ - { - "type": "reference", - "value": "IPaneBodyPart", - "source": "dockview-core" - } - ] - }, - "flags": {} - }, - { - "name": "header", - "code": "FrameworkFactory", - "kind": "property", - "type": { - "type": "reference", - "value": "FrameworkFactory", - "source": "dockview-core", - "typeArguments": [ - { - "type": "reference", - "value": "IPaneHeaderPart", - "source": "dockview-core" - } - ] - }, - "flags": {} - } - ] - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "headerComponents", - "code": "", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "", - "kind": "typeLiteral" - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "headerframeworkComponents", - "code": "", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "", - "kind": "typeLiteral" - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "showDndOverlay", - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "typeLiteral", - "signatures": [ - { - "name": "__type", - "typeParameters": [], - "parameters": [ - { - "name": "event", - "code": "event: PaneviewDndOverlayEvent", - "type": { - "type": "reference", - "value": "PaneviewDndOverlayEvent", - "source": "dockview-core" - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "boolean" - }, - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "callSignature" - } - ] - } - }, - "flags": { - "isOptional": true - } - } - ], - "extends": [] - }, "PaneviewDndOverlayEvent": { "kind": "interface", "name": "PaneviewDndOverlayEvent", @@ -39121,6 +38810,185 @@ "DroptargetEvent" ] }, + "PaneviewFrameworkOptions": { + "kind": "interface", + "name": "PaneviewFrameworkOptions", + "children": [ + { + "name": "createComponent", + "code": "(options: CreateComponentOptions): IPanePart", + "kind": "property", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "(options: CreateComponentOptions): IPanePart", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [ + { + "name": "options", + "code": "options: CreateComponentOptions", + "type": { + "type": "reference", + "value": "CreateComponentOptions", + "source": "dockview-core" + }, + "kind": "parameter" + } + ], + "returnType": { + "type": "reference", + "value": "IPanePart", + "source": "dockview-core" + }, + "code": "(options: CreateComponentOptions): IPanePart", + "kind": "callSignature" + } + ] + } + }, + "flags": {} + }, + { + "name": "createHeaderComponent", + "code": "(options: CreateComponentOptions): IPanePart | undefined", + "kind": "property", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "(options: CreateComponentOptions): IPanePart | undefined", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [ + { + "name": "options", + "code": "options: CreateComponentOptions", + "type": { + "type": "reference", + "value": "CreateComponentOptions", + "source": "dockview-core" + }, + "kind": "parameter" + } + ], + "returnType": { + "type": "or", + "values": [ + { + "type": "reference", + "value": "IPanePart", + "source": "dockview-core" + }, + { + "type": "intrinsic", + "value": "undefined" + } + ] + }, + "code": "(options: CreateComponentOptions): IPanePart | undefined", + "kind": "callSignature" + } + ] + } + }, + "flags": { + "isOptional": true + } + } + ], + "extends": [] + }, + "PaneviewOptions": { + "kind": "interface", + "name": "PaneviewOptions", + "children": [ + { + "name": "className", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "disableAutoResizing", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "disableDnd", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "showDndOverlay", + "code": "(event: PaneviewDndOverlayEvent): boolean", + "kind": "property", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "(event: PaneviewDndOverlayEvent): boolean", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [ + { + "name": "event", + "code": "event: PaneviewDndOverlayEvent", + "type": { + "type": "reference", + "value": "PaneviewDndOverlayEvent", + "source": "dockview-core" + }, + "kind": "parameter" + } + ], + "returnType": { + "type": "intrinsic", + "value": "boolean" + }, + "code": "(event: PaneviewDndOverlayEvent): boolean", + "kind": "callSignature" + } + ] + } + }, + "flags": { + "isOptional": true + } + } + ], + "extends": [] + }, "PaneviewPanelApi": { "kind": "interface", "name": "PaneviewPanelApi", @@ -40579,8 +40447,7 @@ "source": "dockview-core" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } }, { @@ -40592,8 +40459,7 @@ "value": "number" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } }, { @@ -40605,9 +40471,7 @@ "value": "Orientation", "source": "dockview-core" }, - "flags": { - "isReadonly": true - } + "flags": {} }, { "name": "proportionalLayout", @@ -40618,8 +40482,7 @@ "value": "boolean" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } }, { @@ -40632,16 +40495,61 @@ "source": "dockview-core" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } } ], "extends": [] }, - "SplitviewComponentOptions": { + "SplitviewFrameworkOptions": { "kind": "interface", - "name": "SplitviewComponentOptions", + "name": "SplitviewFrameworkOptions", + "children": [ + { + "name": "createComponent", + "code": "(options: CreateComponentOptions): SplitviewPanel", + "kind": "property", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "(options: CreateComponentOptions): SplitviewPanel", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [ + { + "name": "options", + "code": "options: CreateComponentOptions", + "type": { + "type": "reference", + "value": "CreateComponentOptions", + "source": "dockview-core" + }, + "kind": "parameter" + } + ], + "returnType": { + "type": "reference", + "value": "SplitviewPanel", + "source": "dockview-core" + }, + "code": "(options: CreateComponentOptions): SplitviewPanel", + "kind": "callSignature" + } + ] + } + }, + "flags": {} + } + ], + "extends": [] + }, + "SplitviewOptions": { + "kind": "interface", + "name": "SplitviewOptions", "children": [ { "name": "className", @@ -40655,22 +40563,6 @@ "isOptional": true } }, - { - "name": "components", - "code": "", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "", - "kind": "typeLiteral" - } - }, - "flags": { - "isOptional": true - } - }, { "name": "descriptor", "code": "ISplitViewDescriptor", @@ -40681,8 +40573,7 @@ "source": "dockview-core" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } }, { @@ -40697,42 +40588,6 @@ "isOptional": true } }, - { - "name": "frameworkComponents", - "code": "", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "", - "kind": "typeLiteral" - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "frameworkWrapper", - "code": "FrameworkFactory", - "kind": "property", - "type": { - "type": "reference", - "value": "FrameworkFactory", - "source": "dockview-core", - "typeArguments": [ - { - "type": "reference", - "value": "SplitviewPanel", - "source": "dockview-core" - } - ] - }, - "flags": { - "isOptional": true - } - }, { "name": "margin", "code": "number", @@ -40742,8 +40597,7 @@ "value": "number" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } }, { @@ -40755,9 +40609,7 @@ "value": "Orientation", "source": "dockview-core" }, - "flags": { - "isReadonly": true - } + "flags": {} }, { "name": "proportionalLayout", @@ -40768,8 +40620,7 @@ "value": "boolean" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } }, { @@ -40782,8 +40633,7 @@ "source": "dockview-core" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } } ], @@ -42028,6 +41878,27 @@ }, "kind": "typeAlias" }, + "GridviewComponentOptions": { + "name": "GridviewComponentOptions", + "code": "GridviewFrameworkOptions & GridviewOptions", + "typeParameters": [], + "type": { + "type": "intersection", + "values": [ + { + "type": "reference", + "value": "GridviewFrameworkOptions", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "GridviewOptions", + "source": "dockview-core" + } + ] + }, + "kind": "typeAlias" + }, "IDockviewGroupPanelPublic": { "name": "IDockviewGroupPanelPublic", "code": "IDockviewGroupPanel", @@ -42179,6 +42050,27 @@ }, "kind": "typeAlias" }, + "PaneviewComponentOptions": { + "name": "PaneviewComponentOptions", + "code": "PaneviewFrameworkOptions & PaneviewOptions", + "typeParameters": [], + "type": { + "type": "intersection", + "values": [ + { + "type": "reference", + "value": "PaneviewFrameworkOptions", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "PaneviewOptions", + "source": "dockview-core" + } + ] + }, + "kind": "typeAlias" + }, "Position": { "name": "Position", "code": "'center' | 'right' | 'left' | 'bottom' | 'top'", @@ -42246,8 +42138,44 @@ }, "kind": "typeAlias" }, - "PROPERTY_KEYS": { - "name": "PROPERTY_KEYS", + "SplitviewComponentOptions": { + "name": "SplitviewComponentOptions", + "code": "SplitviewFrameworkOptions & SplitviewOptions", + "typeParameters": [], + "type": { + "type": "intersection", + "values": [ + { + "type": "reference", + "value": "SplitviewFrameworkOptions", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "SplitviewOptions", + "source": "dockview-core" + } + ] + }, + "kind": "typeAlias" + }, + "PROPERTY_KEYS_DOCKVIEW": { + "name": "PROPERTY_KEYS_DOCKVIEW", + "code": "", + "kind": "variable" + }, + "PROPERTY_KEYS_GRIDVIEW": { + "name": "PROPERTY_KEYS_GRIDVIEW", + "code": "", + "kind": "variable" + }, + "PROPERTY_KEYS_PANEVIEW": { + "name": "PROPERTY_KEYS_PANEVIEW", + "code": "", + "kind": "variable" + }, + "PROPERTY_KEYS_SPLITVIEW": { + "name": "PROPERTY_KEYS_SPLITVIEW", "code": "", "kind": "variable" }, @@ -43021,18 +42949,6 @@ "kind": "interface", "name": "IDockviewReactProps", "children": [ - { - "name": "className", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": { - "isOptional": true - } - }, { "name": "components", "code": "Record>", @@ -43363,18 +43279,6 @@ "kind": "interface", "name": "IGridviewReactProps", "children": [ - { - "name": "className", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": { - "isOptional": true - } - }, { "name": "components", "code": "Record>>", @@ -43410,30 +43314,6 @@ }, "flags": {} }, - { - "name": "disableAutoResizing", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "hideBorders", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, { "name": "onReady", "code": "(event: GridviewReadyEvent): void", @@ -43471,44 +43351,11 @@ } }, "flags": {} - }, - { - "name": "orientation", - "code": "Orientation.VERTICAL | Orientation.HORIZONTAL", - "kind": "property", - "type": { - "type": "or", - "values": [ - { - "type": "reference", - "value": "Orientation.VERTICAL", - "source": "dockview-core" - }, - { - "type": "reference", - "value": "Orientation.HORIZONTAL", - "source": "dockview-core" - } - ] - }, - "flags": { - "isOptional": true - } - }, - { - "name": "proportionalLayout", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } } ], - "extends": [] + "extends": [ + "GridviewOptions" + ] }, "IPaneviewPanelProps": { "kind": "interface", @@ -43567,18 +43414,6 @@ "kind": "interface", "name": "IPaneviewReactProps", "children": [ - { - "name": "className", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": { - "isOptional": true - } - }, { "name": "components", "code": "Record>>", @@ -43614,30 +43449,6 @@ }, "flags": {} }, - { - "name": "disableAutoResizing", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "disableDnd", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, { "name": "headerComponents", "code": "Record>>", @@ -43783,7 +43594,9 @@ ] } ], - "extends": [] + "extends": [ + "PaneviewOptions" + ] }, "ISplitviewPanelProps": { "kind": "interface", @@ -43832,18 +43645,6 @@ "kind": "interface", "name": "ISplitviewReactProps", "children": [ - { - "name": "className", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": { - "isOptional": true - } - }, { "name": "components", "code": "Record>>", @@ -43879,30 +43680,6 @@ }, "flags": {} }, - { - "name": "disableAutoResizing", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "hideBorders", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, { "name": "onReady", "code": "(event: SplitviewReadyEvent): void", @@ -43940,44 +43717,11 @@ } }, "flags": {} - }, - { - "name": "orientation", - "code": "Orientation.VERTICAL | Orientation.HORIZONTAL", - "kind": "property", - "type": { - "type": "or", - "values": [ - { - "type": "reference", - "value": "Orientation.VERTICAL", - "source": "dockview-core" - }, - { - "type": "reference", - "value": "Orientation.HORIZONTAL", - "source": "dockview-core" - } - ] - }, - "flags": { - "isOptional": true - } - }, - { - "name": "proportionalLayout", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } } ], - "extends": [] + "extends": [ + "SplitviewOptions" + ] }, "PanelParameters": { "kind": "interface", @@ -44159,6 +43903,11 @@ }, "kind": "typeAlias" }, + "ReactPartContext": { + "name": "ReactPartContext", + "code": "", + "kind": "variable" + }, "DockviewDefaultTab": { "kind": "function", "name": "DockviewDefaultTab", @@ -44712,7 +44461,7 @@ "summary": [ { "kind": "text", - "text": "A React Hook that returns an array of portals to be rendered by the user of this hook\nand a disposable function to add a portal. Calling dispose removes this portal from the\nportal array" + "text": "A React Hook that returns an array of portals to be rendered by the user of this hook\r\nand a disposable function to add a portal. Calling dispose removes this portal from the\r\nportal array" } ] }, From cc5037d208cb85a591990001891ce1c498614084 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Fri, 27 Dec 2024 19:52:23 +0000 Subject: [PATCH 30/83] bug: fix classname enablement --- .../components/titlebar/tabsContainer.spec.ts | 32 ++++++++ .../components/titlebar/tabsContainer.ts | 74 ++++++++----------- 2 files changed, 64 insertions(+), 42 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts index b6e4fa861..eee78a588 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts @@ -10,6 +10,7 @@ import { fireEvent } from '@testing-library/dom'; import { TestPanel } from '../../dockviewGroupPanelModel.spec'; import { IDockviewPanel } from '../../../../dockview/dockviewPanel'; import { fromPartial } from '@total-typescript/shoehorn'; +import { DockviewPanelApi } from '../../../../api/dockviewPanelApi'; describe('tabsContainer', () => { test('that an external event does not render a drop target and calls through to the group mode', () => { @@ -815,4 +816,35 @@ describe('tabsContainer', () => { expect(result).toBeTruthy(); expect(result!.childNodes.length).toBe(0); }); + + test('class dv-single-tab is present when only one tab exists`', () => { + const cut = new TabsContainer( + fromPartial({ + options: {}, + }), + fromPartial({}) + ); + + expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy(); + + const panel1 = new TestPanel( + 'panel_1', + fromPartial({}) + ); + cut.openPanel(panel1); + expect(cut.element.classList.contains('dv-single-tab')).toBeTruthy(); + + const panel2 = new TestPanel( + 'panel_2', + fromPartial({}) + ); + cut.openPanel(panel2); + expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy(); + + cut.closePanel(panel1); + expect(cut.element.classList.contains('dv-single-tab')).toBeTruthy(); + + cut.closePanel(panel2); + expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy(); + }); }); diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index c30d1175e..d3bd0568b 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -205,24 +205,6 @@ export class TabsContainer this._element.appendChild(this.rightActionsContainer); this.addDisposables( - this.accessor.onDidAddPanel((e) => { - if (e.api.group === this.group) { - toggleClass( - this._element, - 'dv-single-tab', - this.size === 1 - ); - } - }), - this.accessor.onDidRemovePanel((e) => { - if (e.api.group === this.group) { - toggleClass( - this._element, - 'dv-single-tab', - this.size === 1 - ); - } - }), this._onWillShowOverlay, this._onDrop, this._onTabDragStart, @@ -296,30 +278,6 @@ export class TabsContainer // noop } - private addTab( - tab: IValueDisposable, - index: number = this.tabs.length - ): void { - if (index < 0 || index > this.tabs.length) { - throw new Error('invalid location'); - } - - this.tabContainer.insertBefore( - tab.value.element, - this.tabContainer.children[index] - ); - - this.tabs = [ - ...this.tabs.slice(0, index), - tab, - ...this.tabs.slice(index), - ]; - - if (this.selectedIndex < 0) { - this.selectedIndex = index; - } - } - public delete(id: string): void { const index = this.tabs.findIndex((tab) => tab.value.panel.id === id); @@ -330,6 +288,8 @@ export class TabsContainer disposable.dispose(); value.dispose(); value.element.remove(); + + this.updateClassnames(); } public setActivePanel(panel: IDockviewPanel): void { @@ -430,4 +390,34 @@ export class TabsContainer this.tabs = []; } + + private addTab( + tab: IValueDisposable, + index: number = this.tabs.length + ): void { + if (index < 0 || index > this.tabs.length) { + throw new Error('invalid location'); + } + + this.tabContainer.insertBefore( + tab.value.element, + this.tabContainer.children[index] + ); + + this.tabs = [ + ...this.tabs.slice(0, index), + tab, + ...this.tabs.slice(index), + ]; + + if (this.selectedIndex < 0) { + this.selectedIndex = index; + } + + this.updateClassnames(); + } + + private updateClassnames(): void { + toggleClass(this._element, 'dv-single-tab', this.size === 1); + } } From 0ca56eaa8abfdbf3e5713e8232036d694d5e1e53 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:09:06 +0000 Subject: [PATCH 31/83] chore: align api with existing components --- .../dockview-core/src/api/component.api.ts | 24 +- .../dockview-core/src/dockview/options.ts | 23 +- packages/dockview-core/src/events.ts | 17 + packages/dockview-core/src/index.ts | 4 +- .../src/paneview/draggablePaneviewPanel.ts | 36 ++- .../dockview-core/src/paneview/options.ts | 29 +- .../src/paneview/paneviewComponent.ts | 36 ++- packages/dockview/src/paneview/paneview.tsx | 11 - packages/docs/src/generated/api.output.json | 300 ++++++++---------- 9 files changed, 239 insertions(+), 241 deletions(-) diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 01bff2a8c..9073ce096 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -40,7 +40,7 @@ import { } from '../dockview/dockviewGroupPanel'; import { Emitter, Event } from '../events'; import { IDockviewPanel } from '../dockview/dockviewPanel'; -import { PaneviewDropEvent } from '../paneview/draggablePaneviewPanel'; +import { PaneviewDidDropEvent } from '../paneview/draggablePaneviewPanel'; import { GroupDragEvent, TabDragEvent, @@ -51,7 +51,10 @@ import { DockviewWillDropEvent, WillShowOverlayLocationEvent, } from '../dockview/dockviewGroupPanelModel'; -import { PaneviewComponentOptions } from '../paneview/options'; +import { + PaneviewComponentOptions, + PaneviewDndOverlayEvent, +} from '../paneview/options'; import { SplitviewComponentOptions } from '../splitview/options'; import { GridviewComponentOptions } from '../gridview/options'; @@ -294,19 +297,12 @@ export class PaneviewApi implements CommonApi { /** * Invoked when a Drag'n'Drop event occurs that the component was unable to handle. Exposed for custom Drag'n'Drop functionality. */ - get onDidDrop(): Event { - const emitter = new Emitter(); + get onDidDrop(): Event { + return this.component.onDidDrop; + } - const disposable = this.component.onDidDrop((e) => { - emitter.fire({ ...e, api: this }); - }); - - emitter.dispose = () => { - disposable.dispose(); - emitter.dispose(); - }; - - return emitter.event; + get onUnhandledDragOverEvent(): Event { + return this.component.onUnhandledDragOverEvent; } constructor(private readonly component: IPaneviewComponent) {} diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 05175190a..3f7b94367 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -16,6 +16,7 @@ import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer'; import { IGroupHeaderProps } from './framework'; import { FloatingGroupOptions } from './dockviewComponent'; import { Contraints } from '../gridview/gridviewPanel'; +import { AcceptableEvent, IAcceptableEvent } from '../events'; export interface IHeaderActionsRenderer extends IDisposable { readonly element: HTMLElement; @@ -65,34 +66,26 @@ export interface DockviewOptions { noPanelsOverlay?: 'emptyGroup' | 'watermark'; } -export interface DockviewDndOverlayEvent { +export interface DockviewDndOverlayEvent extends IAcceptableEvent { nativeEvent: DragEvent; target: DockviewGroupDropLocation; position: Position; group?: DockviewGroupPanel; getData: () => PanelTransfer | undefined; - // - isAccepted: boolean; - accept(): void; } -export class DockviewUnhandledDragOverEvent implements DockviewDndOverlayEvent { - private _isAccepted = false; - - get isAccepted(): boolean { - return this._isAccepted; - } - +export class DockviewUnhandledDragOverEvent + extends AcceptableEvent + implements DockviewDndOverlayEvent +{ constructor( readonly nativeEvent: DragEvent, readonly target: DockviewGroupDropLocation, readonly position: Position, readonly getData: () => PanelTransfer | undefined, readonly group?: DockviewGroupPanel - ) {} - - accept(): void { - this._isAccepted = true; + ) { + super(); } } diff --git a/packages/dockview-core/src/events.ts b/packages/dockview-core/src/events.ts index 5c7d0d260..d9cbf32d6 100644 --- a/packages/dockview-core/src/events.ts +++ b/packages/dockview-core/src/events.ts @@ -41,6 +41,23 @@ export class DockviewEvent implements IDockviewEvent { } } +export interface IAcceptableEvent { + readonly isAccepted: boolean; + accept(): void; +} + +export class AcceptableEvent implements IAcceptableEvent { + private _isAccepted = false; + + get isAccepted(): boolean { + return this._isAccepted; + } + + accept(): void { + this._isAccepted = true; + } +} + class LeakageMonitor { readonly events = new Map, Stacktrace>(); diff --git a/packages/dockview-core/src/index.ts b/packages/dockview-core/src/index.ts index dfaec589f..9764f658a 100644 --- a/packages/dockview-core/src/index.ts +++ b/packages/dockview-core/src/index.ts @@ -41,7 +41,7 @@ export * from './gridview/baseComponentGridview'; export { DraggablePaneviewPanel, - PaneviewDropEvent, + PaneviewDidDropEvent as PaneviewDropEvent, } from './paneview/draggablePaneviewPanel'; export * from './dockview/components/panel/content'; @@ -80,6 +80,8 @@ export { PaneviewOptions, PaneviewFrameworkOptions, PROPERTY_KEYS_PANEVIEW, + PaneviewUnhandledDragOverEvent, + PaneviewDndOverlayEvent, } from './paneview/options'; export * from './gridview/gridviewPanel'; diff --git a/packages/dockview-core/src/paneview/draggablePaneviewPanel.ts b/packages/dockview-core/src/paneview/draggablePaneviewPanel.ts index b86c06d33..7227ea46a 100644 --- a/packages/dockview-core/src/paneview/draggablePaneviewPanel.ts +++ b/packages/dockview-core/src/paneview/draggablePaneviewPanel.ts @@ -6,9 +6,13 @@ import { PaneTransfer, } from '../dnd/dataTransfer'; import { Droptarget, DroptargetEvent } from '../dnd/droptarget'; -import { Emitter } from '../events'; +import { Emitter, Event } from '../events'; import { IDisposable } from '../lifecycle'; import { Orientation } from '../splitview/splitview'; +import { + PaneviewDndOverlayEvent, + PaneviewUnhandledDragOverEvent, +} from './options'; import { IPaneviewComponent } from './paneviewComponent'; import { IPaneviewPanel, @@ -16,7 +20,7 @@ import { PaneviewPanel, } from './paneviewPanel'; -export interface PaneviewDropEvent extends DroptargetEvent { +export interface PaneviewDidDropEvent extends DroptargetEvent { panel: IPaneviewPanel; getData: () => PaneTransfer | undefined; api: PaneviewApi; @@ -26,9 +30,14 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel { private handler: DragHandler | undefined; private target: Droptarget | undefined; - private readonly _onDidDrop = new Emitter(); + private readonly _onDidDrop = new Emitter(); readonly onDidDrop = this._onDidDrop.event; + private readonly _onUnhandledDragOverEvent = + new Emitter(); + readonly onUnhandledDragOverEvent: Event = + this._onUnhandledDragOverEvent.event; + constructor( private readonly accessor: IPaneviewComponent, id: string, @@ -40,6 +49,8 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel { ) { super(id, component, headerComponent, orientation, isExpanded, true); + this.addDisposables(this._onDidDrop, this._onUnhandledDragOverEvent); + if (!disableDnd) { this.initDragFeatures(); } @@ -76,7 +87,7 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel { overlayModel: { activationSize: { type: 'percentage', value: 50 }, }, - canDisplayOverlay: (event) => { + canDisplayOverlay: (event, position) => { const data = getPaneData(); if (data) { @@ -88,15 +99,16 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel { } } - if (this.accessor.options.showDndOverlay) { - return this.accessor.options.showDndOverlay({ - nativeEvent: event, - getData: getPaneData, - panel: this, - }); - } + const firedEvent = new PaneviewUnhandledDragOverEvent( + event, + position, + getPaneData, + this + ); - return false; + this._onUnhandledDragOverEvent.fire(firedEvent); + + return firedEvent.isAccepted; }, }); diff --git a/packages/dockview-core/src/paneview/options.ts b/packages/dockview-core/src/paneview/options.ts index f7702638a..f51e28dce 100644 --- a/packages/dockview-core/src/paneview/options.ts +++ b/packages/dockview-core/src/paneview/options.ts @@ -1,11 +1,12 @@ +import { PaneTransfer } from '../dnd/dataTransfer'; +import { Position } from '../dnd/droptarget'; import { CreateComponentOptions } from '../dockview/options'; -import { PaneviewDndOverlayEvent } from './paneviewComponent'; -import { IPanePart } from './paneviewPanel'; +import { AcceptableEvent, IAcceptableEvent } from '../events'; +import { IPanePart, IPaneviewPanel } from './paneviewPanel'; export interface PaneviewOptions { disableAutoResizing?: boolean; disableDnd?: boolean; - showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean; className?: string; } @@ -27,9 +28,29 @@ export const PROPERTY_KEYS_PANEVIEW: (keyof PaneviewOptions)[] = (() => { const properties: Record = { disableAutoResizing: undefined, disableDnd: undefined, - showDndOverlay: undefined, className: undefined, }; return Object.keys(properties) as (keyof PaneviewOptions)[]; })(); + +export interface PaneviewDndOverlayEvent extends IAcceptableEvent { + nativeEvent: DragEvent; + position: Position; + panel: IPaneviewPanel; + getData: () => PaneTransfer | undefined; +} + +export class PaneviewUnhandledDragOverEvent + extends AcceptableEvent + implements PaneviewDndOverlayEvent +{ + constructor( + readonly nativeEvent: DragEvent, + readonly position: Position, + readonly getData: () => PaneTransfer | undefined, + readonly panel: IPaneviewPanel + ) { + super(); + } +} diff --git a/packages/dockview-core/src/paneview/paneviewComponent.ts b/packages/dockview-core/src/paneview/paneviewComponent.ts index 651f5f406..2ee9d5820 100644 --- a/packages/dockview-core/src/paneview/paneviewComponent.ts +++ b/packages/dockview-core/src/paneview/paneviewComponent.ts @@ -6,12 +6,12 @@ import { MutableDisposable, } from '../lifecycle'; import { LayoutPriority, Orientation, Sizing } from '../splitview/splitview'; -import { PaneviewComponentOptions } from './options'; +import { PaneviewComponentOptions, PaneviewDndOverlayEvent } from './options'; import { Paneview } from './paneview'; import { IPanePart, PaneviewPanel, IPaneviewPanel } from './paneviewPanel'; import { DraggablePaneviewPanel, - PaneviewDropEvent, + PaneviewDidDropEvent, } from './draggablePaneviewPanel'; import { DefaultHeader } from './defaultPaneviewHeader'; import { sequentialNumberGenerator } from '../math'; @@ -22,12 +22,6 @@ import { Classnames } from '../dom'; const nextLayoutId = sequentialNumberGenerator(); -export interface PaneviewDndOverlayEvent { - nativeEvent: DragEvent; - panel: IPaneviewPanel; - getData: () => PaneTransfer | undefined; -} - export interface SerializedPaneviewPanel { snap?: boolean; priority?: LayoutPriority; @@ -106,9 +100,10 @@ export interface IPaneviewComponent extends IDisposable { readonly options: PaneviewComponentOptions; readonly onDidAddView: Event; readonly onDidRemoveView: Event; - readonly onDidDrop: Event; + readonly onDidDrop: Event; readonly onDidLayoutChange: Event; readonly onDidLayoutFromJSON: Event; + readonly onUnhandledDragOverEvent: Event; addPanel( options: AddPaneviewComponentOptions ): IPaneviewPanel; @@ -137,8 +132,8 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { private readonly _onDidLayoutChange = new Emitter(); readonly onDidLayoutChange: Event = this._onDidLayoutChange.event; - private readonly _onDidDrop = new Emitter(); - readonly onDidDrop: Event = this._onDidDrop.event; + private readonly _onDidDrop = new Emitter(); + readonly onDidDrop: Event = this._onDidDrop.event; private readonly _onDidAddView = new Emitter(); readonly onDidAddView = this._onDidAddView.event; @@ -146,6 +141,11 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { private readonly _onDidRemoveView = new Emitter(); readonly onDidRemoveView = this._onDidRemoveView.event; + private readonly _onUnhandledDragOverEvent = + new Emitter(); + readonly onUnhandledDragOverEvent: Event = + this._onUnhandledDragOverEvent.event; + private readonly _classNames: Classnames; get id(): string { @@ -204,7 +204,8 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { this._onDidLayoutfromJSON, this._onDidDrop, this._onDidAddView, - this._onDidRemoveView + this._onDidRemoveView, + this._onUnhandledDragOverEvent ); this._classNames = new Classnames(this.element); @@ -442,9 +443,14 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { } private doAddPanel(panel: PaneFramework): void { - const disposable = panel.onDidDrop((event) => { - this._onDidDrop.fire(event); - }); + const disposable = new CompositeDisposable( + panel.onDidDrop((event) => { + this._onDidDrop.fire(event); + }), + panel.onUnhandledDragOverEvent((event) => { + this._onUnhandledDragOverEvent.fire(event); + }) + ); this._viewDisposables.set(panel.id, disposable); } diff --git a/packages/dockview/src/paneview/paneview.tsx b/packages/dockview/src/paneview/paneview.tsx index 2dc623638..34ab300b9 100644 --- a/packages/dockview/src/paneview/paneview.tsx +++ b/packages/dockview/src/paneview/paneview.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { PaneviewPanelApi, - PaneviewDndOverlayEvent, PaneviewApi, PaneviewDropEvent, createPaneview, @@ -32,7 +31,6 @@ export interface IPaneviewReactProps extends PaneviewOptions { string, React.FunctionComponent >; - showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean; onDidDrop?(event: PaneviewDropEvent): void; } @@ -177,15 +175,6 @@ export const PaneviewReact = React.forwardRef( }; }, [props.onDidDrop]); - React.useEffect(() => { - if (!paneviewRef.current) { - return; - } - paneviewRef.current.updateOptions({ - showDndOverlay: props.showDndOverlay, - }); - }, [props.showDndOverlay]); - return (
", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "PaneviewDndOverlayEvent", + "source": "dockview-core" + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "part", "code": "IFrameworkPart", @@ -15115,6 +15137,26 @@ "isReadonly": true } }, + { + "name": "onUnhandledDragOverEvent", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "PaneviewDndOverlayEvent", + "source": "dockview-core" + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "part", "code": "IFrameworkPart", @@ -16584,6 +16626,28 @@ ] } }, + { + "name": "onUnhandledDragOverEvent", + "code": "Event", + "kind": "accessor", + "value": { + "name": "onUnhandledDragOverEvent", + "code": "Event", + "kind": "getSignature", + "returnType": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "PaneviewDndOverlayEvent", + "source": "dockview-core" + } + ] + } + } + }, { "name": "panels", "code": "IPaneviewPanel[]", @@ -17292,6 +17356,26 @@ "isReadonly": true } }, + { + "name": "onUnhandledDragOverEvent", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "PaneviewDndOverlayEvent", + "source": "dockview-core" + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "disableResizing", "code": "boolean", @@ -19487,7 +19571,7 @@ "summary": [ { "kind": "text", - "text": "Invoked whenever any aspect of the layout changes.\r\nIf listening to this event it may be worth debouncing ouputs." + "text": "Invoked whenever any aspect of the layout changes.\nIf listening to this event it may be worth debouncing ouputs." } ] }, @@ -19509,7 +19593,7 @@ "summary": [ { "kind": "text", - "text": "Invoked whenever any aspect of the layout changes.\r\nIf listening to this event it may be worth debouncing ouputs." + "text": "Invoked whenever any aspect of the layout changes.\nIf listening to this event it may be worth debouncing ouputs." } ] } @@ -20105,7 +20189,7 @@ }, { "kind": "text", - "text": " method\r\nfor the subsequent resize." + "text": " method\nfor the subsequent resize." } ] }, @@ -20167,7 +20251,7 @@ }, { "kind": "text", - "text": " method\r\nfor the subsequent resize." + "text": " method\nfor the subsequent resize." } ] } @@ -23092,7 +23176,9 @@ "type": "intrinsic", "value": "boolean" }, - "flags": {} + "flags": { + "isReadonly": true + } }, { "name": "nativeEvent", @@ -23146,7 +23232,9 @@ ] } ], - "extends": [] + "extends": [ + "IAcceptableEvent" + ] }, "DockviewFrameworkOptions": { "kind": "interface", @@ -24288,7 +24376,7 @@ }, { "kind": "text", - "text": ".\r\nCall " + "text": ".\nCall " }, { "kind": "code", @@ -35161,6 +35249,26 @@ "isReadonly": true } }, + { + "name": "onUnhandledDragOverEvent", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "PaneviewDndOverlayEvent", + "source": "dockview-core" + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "options", "code": "PaneviewComponentOptions", @@ -38650,72 +38758,6 @@ "PanelInitParameters" ] }, - "PaneviewDndOverlayEvent": { - "kind": "interface", - "name": "PaneviewDndOverlayEvent", - "children": [ - { - "name": "getData", - "code": "(): PaneTransfer | undefined", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "(): PaneTransfer | undefined", - "kind": "typeLiteral", - "signatures": [ - { - "name": "__type", - "typeParameters": [], - "parameters": [], - "returnType": { - "type": "or", - "values": [ - { - "type": "reference", - "value": "PaneTransfer", - "source": "dockview-core" - }, - { - "type": "intrinsic", - "value": "undefined" - } - ] - }, - "code": "(): PaneTransfer | undefined", - "kind": "callSignature" - } - ] - } - }, - "flags": {} - }, - { - "name": "nativeEvent", - "code": "DragEvent", - "kind": "property", - "type": { - "type": "reference", - "value": "DragEvent", - "source": "typescript" - }, - "flags": {} - }, - { - "name": "panel", - "code": "IPaneviewPanel", - "kind": "property", - "type": { - "type": "reference", - "value": "IPaneviewPanel", - "source": "dockview-core" - }, - "flags": {} - } - ], - "extends": [] - }, "PaneviewDropEvent": { "kind": "interface", "name": "PaneviewDropEvent", @@ -38945,46 +38987,6 @@ "flags": { "isOptional": true } - }, - { - "name": "showDndOverlay", - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "typeLiteral", - "signatures": [ - { - "name": "__type", - "typeParameters": [], - "parameters": [ - { - "name": "event", - "code": "event: PaneviewDndOverlayEvent", - "type": { - "type": "reference", - "value": "PaneviewDndOverlayEvent", - "source": "dockview-core" - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "boolean" - }, - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "callSignature" - } - ] - } - }, - "flags": { - "isOptional": true - } } ], "extends": [] @@ -41389,7 +41391,7 @@ "summary": [ { "kind": "text", - "text": "If true then add the panel without setting it as the active panel.\r\n\r\nDefaults to " + "text": "If true then add the panel without setting it as the active panel.\n\nDefaults to " }, { "kind": "code", @@ -41456,7 +41458,7 @@ "summary": [ { "kind": "text", - "text": "The rendering mode of the panel.\r\n\r\nThis dictates what happens to the HTML of the panel when it is hidden." + "text": "The rendering mode of the panel.\n\nThis dictates what happens to the HTML of the panel when it is hidden." } ] } @@ -41496,7 +41498,7 @@ "summary": [ { "kind": "text", - "text": "The title for the panel which can be accessed within both the tab and component.\r\n\r\nIf using the default tab renderer this title will be displayed in the tab." + "text": "The title for the panel which can be accessed within both the tab and component.\n\nIf using the default tab renderer this title will be displayed in the tab." } ] } @@ -43524,46 +43526,6 @@ }, "flags": {} }, - { - "name": "showDndOverlay", - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "typeLiteral", - "signatures": [ - { - "name": "__type", - "typeParameters": [], - "parameters": [ - { - "name": "event", - "code": "event: PaneviewDndOverlayEvent", - "type": { - "type": "reference", - "value": "PaneviewDndOverlayEvent", - "source": "dockview-core" - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "boolean" - }, - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "callSignature" - } - ] - } - }, - "flags": { - "isOptional": true - } - }, { "name": "onDidDrop", "code": "(event: PaneviewDropEvent): void", From 5173922c696fff3f759357fbcfd262157b5a866b Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:29:48 +0000 Subject: [PATCH 32/83] chore: docs --- packages/docs/docs/advanced/nested.mdx | 2 +- packages/docs/docs/core/dnd/dragAndDrop.mdx | 22 ++++++++++--------- .../components/paneview/paneview.mdx | 2 +- .../react/dockview/dnd-external/src/app.tsx | 10 ++++----- .../dockview/dnd-external/react/src/app.tsx | 9 ++++---- .../dockview/nested/react/src/app.tsx | 12 ---------- 6 files changed, 23 insertions(+), 34 deletions(-) diff --git a/packages/docs/docs/advanced/nested.mdx b/packages/docs/docs/advanced/nested.mdx index 49161a44a..66f11c897 100644 --- a/packages/docs/docs/advanced/nested.mdx +++ b/packages/docs/docs/advanced/nested.mdx @@ -8,6 +8,6 @@ import { CodeRunner } from '@site/src/components/ui/codeRunner'; # Nested Dockviews You can safely create multiple dockview instances within one page and nest dockviews within other dockviews. -If you wish to interact with the drop event from one dockview instance in another dockview instance you can implement the `showDndOverlay` and `onDidDrop` props on `DockviewReact`. +If you wish to interact with the drop event from one dockview instance in another dockview instance you can implement the `api.onUnhandledDragOverEvent` and `onDidDrop` props on `DockviewReact`. diff --git a/packages/docs/docs/core/dnd/dragAndDrop.mdx b/packages/docs/docs/core/dnd/dragAndDrop.mdx index ff73060ec..33293c4c2 100644 --- a/packages/docs/docs/core/dnd/dragAndDrop.mdx +++ b/packages/docs/docs/core/dnd/dragAndDrop.mdx @@ -43,7 +43,7 @@ For interaction with the Drag events directly the component exposes some method ```tsx /** * called when an ondrop event which does not originate from the dockview libray and - * passes the showDndOverlay condition occurs + * passes the onUnhandledDragOverEvent condition **/ const onDidDrop = (event: DockviewDropEvent) => { const { group } = event; @@ -58,14 +58,17 @@ const onDidDrop = (event: DockviewDropEvent) => { }); }; -/** - * called for drag over events which do not originate from the dockview library - * allowing the developer to decide where the overlay should be shown for a - * particular drag event - **/ -const showDndOverlay = (event: DockviewDndOverlayEvent) => { - return true; -}; +const onReady = (event: DockviewReadyEvent) => { + + /** + * called for drag over events which do not originate from the dockview library + * allowing the developer to decide where the overlay should be shown for a + * particular drag event + **/ + api.onUnhandledDragOverEvent(event => { + event.accept(); + }); +} return ( ); ``` diff --git a/packages/docs/old_docs_DELETE_SOON/components/paneview/paneview.mdx b/packages/docs/old_docs_DELETE_SOON/components/paneview/paneview.mdx index 95e025478..b88776771 100644 --- a/packages/docs/old_docs_DELETE_SOON/components/paneview/paneview.mdx +++ b/packages/docs/old_docs_DELETE_SOON/components/paneview/paneview.mdx @@ -221,7 +221,7 @@ If you provide the `PaneviewReact` component with the prop `onDidDrop` you will You can safely create multiple paneview instances within one page. They will not interact with each other by default. -If you wish to interact with the drop event from one paneview instance in another paneview instance you can implement the `showDndOverlay` and `onDidDrop` props on `PaneviewReact`. +If you wish to interact with the drop event from one paneview instance in another paneview instance you can implement the `api.onUnhandledDragOverEvent` and `onDidDrop` props on `PaneviewReact`. As an example see how dragging a header from one control to another will only trigger an interactable event for the developer if the checkbox is enabled. diff --git a/packages/docs/sandboxes/react/dockview/dnd-external/src/app.tsx b/packages/docs/sandboxes/react/dockview/dnd-external/src/app.tsx index 8d3c70cad..94bf96a86 100644 --- a/packages/docs/sandboxes/react/dockview/dnd-external/src/app.tsx +++ b/packages/docs/sandboxes/react/dockview/dnd-external/src/app.tsx @@ -113,7 +113,12 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { } }); + const disposable = api.onUnhandledDragOverEvent((event) => { + event.accept(); + }); + return () => { + disposable.dispose(); panelDragDisposable.dispose(); groupDragDisposable.dispose(); }; @@ -134,10 +139,6 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { }); }; - const showDndOverlay = (event: DockviewDndOverlayEvent) => { - return true; - }; - const onDrop = (event: React.DragEvent) => { const dataTransfer = event.dataTransfer; @@ -179,7 +180,6 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { onReady={onReady} className={`${props.theme || 'dockview-theme-abyss'}`} onDidDrop={onDidDrop} - showDndOverlay={showDndOverlay} rootOverlayModel={{ size: { value: 100, type: 'pixels' }, activationSize: { value: 5, type: 'percentage' }, diff --git a/packages/docs/templates/dockview/dnd-external/react/src/app.tsx b/packages/docs/templates/dockview/dnd-external/react/src/app.tsx index cb04f50a5..19473533e 100644 --- a/packages/docs/templates/dockview/dnd-external/react/src/app.tsx +++ b/packages/docs/templates/dockview/dnd-external/react/src/app.tsx @@ -113,6 +113,10 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { } }); + const disposable = api.onUnhandledDragOverEvent((event) => { + event.accept(); + }); + return () => { panelDragDisposable.dispose(); groupDragDisposable.dispose(); @@ -134,10 +138,6 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { }); }; - const showDndOverlay = (event: DockviewDndOverlayEvent) => { - return true; - }; - const onDrop = (event: React.DragEvent) => { const dataTransfer = event.dataTransfer; @@ -179,7 +179,6 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { onReady={onReady} className={`${props.theme || 'dockview-theme-abyss'}`} onDidDrop={onDidDrop} - showDndOverlay={showDndOverlay} rootOverlayModel={{ size: { value: 100, type: 'pixels' }, activationSize: { value: 5, type: 'percentage' }, diff --git a/packages/docs/templates/dockview/nested/react/src/app.tsx b/packages/docs/templates/dockview/nested/react/src/app.tsx index 42afc7f73..5c033b453 100644 --- a/packages/docs/templates/dockview/nested/react/src/app.tsx +++ b/packages/docs/templates/dockview/nested/react/src/app.tsx @@ -70,23 +70,11 @@ const NestedDockview = (props: { theme?: string }) => { }); }; - const showDndOverlay = (event: DockviewDndOverlayEvent) => { - // console.log(event.getData()); - - return false; - }; - - const onDidDrop = (event: DockviewDidDropEvent) => { - // event.getData(); - }; - return ( ); }; From 20eb30ed1c6cd132794c55cacb70927406433c22 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:30:30 +0000 Subject: [PATCH 33/83] chore: docs config --- packages/docs/docusaurus.config.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/docs/docusaurus.config.js b/packages/docs/docusaurus.config.js index a1ad4ae14..6a4bbd74c 100644 --- a/packages/docs/docusaurus.config.js +++ b/packages/docs/docusaurus.config.js @@ -107,8 +107,8 @@ const config = { sidebarPath: require.resolve('./sidebars.js'), // Please change this to your repo. // Remove this to remove the "edit this page" links. - editUrl: - 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', + // editUrl: + // 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', // versions: { // current: { // label: `Development 🚧`, @@ -116,11 +116,11 @@ const config = { // }, }, blog: { - showReadingTime: true, + // showReadingTime: true, // Please change this to your repo. // Remove this to remove the "edit this page" links. - editUrl: - 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', + // editUrl: + // 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', }, theme: { customCss: require.resolve('./src/css/custom.scss'), @@ -210,8 +210,8 @@ const config = { title: 'Learn', items: [ { - label: 'Docs', - to: '/docs', + label: 'Demo', + to: '/demo', }, ], }, From cb45889acadbe2e852cdf4db1ff760249c8cd59b Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 29 Dec 2024 13:55:54 +0000 Subject: [PATCH 34/83] chore: sonar fixes --- .../src/__tests__/dockview/dockviewComponent.spec.ts | 1 - packages/dockview-core/src/api/component.api.ts | 2 +- packages/dockview-core/src/paneview/paneviewComponent.ts | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 2c4f09c10..0be413221 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -5082,7 +5082,6 @@ describe('dockviewComponent', () => { '.dv-content-container > .testpanel-panel_3' ).length ).toBe(0); - expect(dockview.element); }); test('move popout group of 1 panel inside grid', async () => { diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 9073ce096..6884c93aa 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -38,7 +38,7 @@ import { DockviewGroupPanel, IDockviewGroupPanel, } from '../dockview/dockviewGroupPanel'; -import { Emitter, Event } from '../events'; +import { Event } from '../events'; import { IDockviewPanel } from '../dockview/dockviewPanel'; import { PaneviewDidDropEvent } from '../paneview/draggablePaneviewPanel'; import { diff --git a/packages/dockview-core/src/paneview/paneviewComponent.ts b/packages/dockview-core/src/paneview/paneviewComponent.ts index 2ee9d5820..86c87a690 100644 --- a/packages/dockview-core/src/paneview/paneviewComponent.ts +++ b/packages/dockview-core/src/paneview/paneviewComponent.ts @@ -15,7 +15,6 @@ import { } from './draggablePaneviewPanel'; import { DefaultHeader } from './defaultPaneviewHeader'; import { sequentialNumberGenerator } from '../math'; -import { PaneTransfer } from '../dnd/dataTransfer'; import { Resizable } from '../resizable'; import { Parameters } from '../panel/types'; import { Classnames } from '../dom'; From 8359596be019a9ad24a3125e5d8567c2c155a4de Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 29 Dec 2024 14:17:13 +0000 Subject: [PATCH 35/83] chore: v3.0.0 docs --- .../docs/blog/2024-12-29-dockview-3.0.0.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 packages/docs/blog/2024-12-29-dockview-3.0.0.md diff --git a/packages/docs/blog/2024-12-29-dockview-3.0.0.md b/packages/docs/blog/2024-12-29-dockview-3.0.0.md new file mode 100644 index 000000000..807a5945e --- /dev/null +++ b/packages/docs/blog/2024-12-29-dockview-3.0.0.md @@ -0,0 +1,29 @@ +--- +slug: dockview-3.0.0-release +title: Dockview 3.0.0 +tags: [release] +--- + +# Release Notes + +This is a major release version due to some breaking changes in the `dockview-core` package. If you use the react or vue versions of dockview you should not see any breaking changes when upgrading. There are no new features in this release. + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +- `dockview-vue` vue3 peerDependency [#808](https://github.com/mathuo/dockview/issues/808) +- Bug: correct enablement of `dv-single-tab` class [#811](https://github.com/mathuo/dockview/issues/811) + +## 🔥 Breaking changes + +- Standardize `dockview-core` components for generic framework extensions following the pattern in `DockviewComponent` [#810](https://github.com/mathuo/dockview/issues/810) + - `SplitviewComponent`: Replace `components` and `frameworkComponents` with `createComponent` + - `PaneviewComponent`: Replace `components` and `frameworkComponents` with `createComponent` and replace `headerComponents` and `headerFrameworkComponents` with `createHeaderComponent` + - `GridviewComponent`: Replace `components` and `frameworkComponents` with `createComponent` +- rename class `dockview-react-part` to `dv-react-part` [#806](https://github.com/mathuo/dockview/issues/806) +- rename type `PaneviewDropEvent` to `PaneviewDidDropEvent` [#812](https://github.com/mathuo/dockview/issues/812) +- remove `showDndOverlay` from `PaneviewComponent` in favour of `api.onUnhandledDragOverEvent` [#812](https://github.com/mathuo/dockview/issues/812) + From 148a05a201c22c44c0f0a6d1ab54cbad17cec525 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 29 Dec 2024 14:17:33 +0000 Subject: [PATCH 36/83] chore(release): publish v3.0.0 --- lerna.json | 2 +- packages/dockview-angular/package.json | 4 ++-- packages/dockview-core/package.json | 2 +- packages/dockview-react/package.json | 4 ++-- packages/dockview-vue/package.json | 4 ++-- packages/dockview/package.json | 4 ++-- packages/docs/package.json | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lerna.json b/lerna.json index 75683ed53..8e89ed7f6 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "2.1.4", + "version": "3.0.0", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-angular/package.json b/packages/dockview-angular/package.json index 7298c7316..410f007d1 100644 --- a/packages/dockview-angular/package.json +++ b/packages/dockview-angular/package.json @@ -1,6 +1,6 @@ { "name": "dockview-angular", - "version": "2.1.4", + "version": "3.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^2.1.4" + "dockview-core": "^3.0.0" } } diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index 859653dbf..ac9d66d86 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "2.1.4", + "version": "3.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", diff --git a/packages/dockview-react/package.json b/packages/dockview-react/package.json index 4d6316852..34ef63def 100644 --- a/packages/dockview-react/package.json +++ b/packages/dockview-react/package.json @@ -1,6 +1,6 @@ { "name": "dockview-react", - "version": "2.1.4", + "version": "3.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage" }, "dependencies": { - "dockview": "^2.1.4" + "dockview": "^3.0.0" } } diff --git a/packages/dockview-vue/package.json b/packages/dockview-vue/package.json index 04395a823..97697e7b9 100644 --- a/packages/dockview-vue/package.json +++ b/packages/dockview-vue/package.json @@ -1,6 +1,6 @@ { "name": "dockview-vue", - "version": "2.1.4", + "version": "3.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -52,7 +52,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage" }, "dependencies": { - "dockview-core": "^2.1.4" + "dockview-core": "^3.0.0" }, "peerDependencies": { "vue": "^3.4.0" diff --git a/packages/dockview/package.json b/packages/dockview/package.json index a65944dcf..aa5fb113b 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "2.1.4", + "version": "3.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,7 +54,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^2.1.4" + "dockview-core": "^3.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/packages/docs/package.json b/packages/docs/package.json index f605b50c5..700febd27 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "2.1.4", + "version": "3.0.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": "^2.1.4", + "dockview": "^3.0.0", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", "react-laag": "^2.0.5", From cd91f4a4d39e934194bceb8d9a9dd8c6746e1cd6 Mon Sep 17 00:00:00 2001 From: zaxer2 Date: Fri, 3 Jan 2025 13:22:07 -0500 Subject: [PATCH 37/83] Updated react dockview-demo CodeSandbox to reset relevant state on subsequent onReady calls Strict Mode causes the Dockview component to render twice, causing onReady to be called twice, which puts two copies of DefaultLayout in state without resetting between calls. A most-ideal solution would probably be to expose an onUnload hook or something like that, to be called after one onReady but before the next. Then, the state resets could be called in that hook. but this fix will at least ensure the Dockview-Demo codesandbox behaves nicely for the time being. --- .../docs/sandboxes/react/dockview/demo-dockview/src/app.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx index a9f4ea97c..0b8701fd6 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx @@ -178,6 +178,11 @@ const DockviewDemo = (props: { theme?: string }) => { const onReady = (event: DockviewReadyEvent) => { setApi(event.api); + setPanels([]); + setGroups([]); + setActivePanel(undefined); + setActiveGroup(undefined); + addLogLine(`Dockview Is Ready`); event.api.onDidAddPanel((event) => { setPanels((_) => [..._, event.id]); From 10fd5778dc5fc78bfa334d1b9090fb9f6e106877 Mon Sep 17 00:00:00 2001 From: zaxer2 Date: Fri, 3 Jan 2025 13:24:28 -0500 Subject: [PATCH 38/83] Copying Sandbox change to corresponding Template see prev. commit --- .../docs/templates/dockview/demo-dockview/react/src/app.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/docs/templates/dockview/demo-dockview/react/src/app.tsx b/packages/docs/templates/dockview/demo-dockview/react/src/app.tsx index 3ab3066da..befc23f44 100644 --- a/packages/docs/templates/dockview/demo-dockview/react/src/app.tsx +++ b/packages/docs/templates/dockview/demo-dockview/react/src/app.tsx @@ -124,6 +124,11 @@ const DockviewDemo = (props: { theme?: string }) => { const onReady = (event: DockviewReadyEvent) => { setApi(event.api); + setPanels([]); + setGroups([]); + setActivePanel(undefined); + setActiveGroup(undefined); + addLogLine(`Dockview Is Ready`); }; React.useEffect(() => { From 2d8d38b466691698323767d2d7d45ca5b7f39860 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 6 Jan 2025 20:14:14 +0000 Subject: [PATCH 39/83] bug: duplicate root container --- packages/dockview-core/src/gridview/baseComponentGridview.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/dockview-core/src/gridview/baseComponentGridview.ts b/packages/dockview-core/src/gridview/baseComponentGridview.ts index 9d02993f3..537435310 100644 --- a/packages/dockview-core/src/gridview/baseComponentGridview.ts +++ b/packages/dockview-core/src/gridview/baseComponentGridview.ts @@ -156,15 +156,13 @@ export abstract class BaseGrid } constructor(parentElement: HTMLElement, options: BaseGridOptions) { - super(document.createElement('div'), options.disableAutoResizing); + super(parentElement, options.disableAutoResizing); this.element.style.height = '100%'; this.element.style.width = '100%'; this._classNames = new Classnames(this.element); this._classNames.setClassNames(options.className ?? ''); - parentElement.appendChild(this.element); - this.gridview = new Gridview( !!options.proportionalLayout, options.styles, From 04e4ea1a70cc4c3d61c5bdaaf1fef7cde67f5ffa Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 6 Jan 2025 20:18:18 +0000 Subject: [PATCH 40/83] tabs overflow menu --- .../src/dockview/components/popupService.ts | 82 ++++ .../dockview/components/titlebar/tabs.scss | 42 ++ .../src/dockview/components/titlebar/tabs.tsx | 263 ++++++++++++ .../components/titlebar/tabsContainer.scss | 61 +-- .../components/titlebar/tabsContainer.ts | 377 ++++++------------ .../src/dockview/dockviewComponent.scss | 10 +- .../src/dockview/dockviewComponent.ts | 4 + .../src/dockview/dockviewPanelModel.ts | 6 + 8 files changed, 532 insertions(+), 313 deletions(-) create mode 100644 packages/dockview-core/src/dockview/components/popupService.ts create mode 100644 packages/dockview-core/src/dockview/components/titlebar/tabs.scss create mode 100644 packages/dockview-core/src/dockview/components/titlebar/tabs.tsx diff --git a/packages/dockview-core/src/dockview/components/popupService.ts b/packages/dockview-core/src/dockview/components/popupService.ts new file mode 100644 index 000000000..1fb5a0996 --- /dev/null +++ b/packages/dockview-core/src/dockview/components/popupService.ts @@ -0,0 +1,82 @@ +import { addDisposableWindowListener } from '../../events'; +import { + CompositeDisposable, + Disposable, + MutableDisposable, +} from '../../lifecycle'; + +export class PopupService extends CompositeDisposable { + private readonly _element: HTMLElement; + private _active: HTMLElement | null = null; + private _activeDisposable = new MutableDisposable(); + + constructor(private readonly root: HTMLElement) { + super(); + + this._element = document.createElement('div'); + this._element.className = 'dv-popover-anchor'; + this._element.style.position = 'relative'; + + this.root.prepend(this._element); + + this.addDisposables( + Disposable.from(() => { + this.close(); + }), + this._activeDisposable + ); + } + + openPopover( + element: HTMLElement, + position: { x: number; y: number } + ): void { + this.close(); + + const wrapper = document.createElement('div'); + wrapper.style.position = 'absolute'; + wrapper.style.zIndex = '99'; + wrapper.appendChild(element); + + const anchorBox = this._element.getBoundingClientRect(); + const offsetX = anchorBox.left; + const offsetY = anchorBox.top; + + wrapper.style.top = `${position.y - offsetY}px`; + wrapper.style.left = `${position.x - offsetX}px`; + + this._element.appendChild(wrapper); + + this._active = wrapper; + + this._activeDisposable.value = new CompositeDisposable( + addDisposableWindowListener(window, 'pointerdown', (event) => { + const target = event.target; + + if (!(target instanceof HTMLElement)) { + return; + } + + let el: HTMLElement | null = target; + + while (el && el !== wrapper) { + el = el?.parentElement ?? null; + } + + if (el) { + return; // clicked within popover + } + + this.close(); + }) + ); + } + + close(): void { + if (this._active) { + this._active.remove(); + this._activeDisposable.dispose(); + this._active = null; + } + } +} diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss new file mode 100644 index 000000000..01b715404 --- /dev/null +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss @@ -0,0 +1,42 @@ +.dv-tabs-container { + display: flex; + overflow-x: overlay; + overflow-y: hidden; + + scrollbar-width: thin; // firefox + + &::-webkit-scrollbar { + height: 3px; + } + + /* Track */ + &::-webkit-scrollbar-track { + background: transparent; + } + + /* Handle */ + &::-webkit-scrollbar-thumb { + background: var(--dv-tabs-container-scrollbar-color); + } + + .dv-tab { + -webkit-user-drag: element; + outline: none; + min-width: 75px; + cursor: pointer; + position: relative; + box-sizing: border-box; + + &:not(:first-child)::before { + content: ' '; + position: absolute; + top: 0; + left: 0; + z-index: 5; + pointer-events: none; + background-color: var(--dv-tab-divider-color); + width: 1px; + height: 100%; + } + } +} diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.tsx b/packages/dockview-core/src/dockview/components/titlebar/tabs.tsx new file mode 100644 index 000000000..0a86dd54b --- /dev/null +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.tsx @@ -0,0 +1,263 @@ +import { getPanelData } from '../../../dnd/dataTransfer'; +import { OverflowObserver } from '../../../dom'; +import { addDisposableListener, Emitter, Event } from '../../../events'; +import { + CompositeDisposable, + Disposable, + IValueDisposable, +} from '../../../lifecycle'; +import { DockviewComponent } from '../../dockviewComponent'; +import { DockviewGroupPanel } from '../../dockviewGroupPanel'; +import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel'; +import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel'; +import { Tab } from '../tab/tab'; +import { TabDragEvent, TabDropIndexEvent } from './tabsContainer'; + +export class Tabs extends CompositeDisposable { + private readonly _element: HTMLElement; + private readonly _tabsList: HTMLElement; + + private tabs: IValueDisposable[] = []; + private selectedIndex = -1; + private _hasOverflow = false; + private _dropdownAnchor: HTMLElement | null = null; + + private readonly _onTabDragStart = new Emitter(); + readonly onTabDragStart: Event = this._onTabDragStart.event; + + private readonly _onDrop = new Emitter(); + readonly onDrop: Event = this._onDrop.event; + + private readonly _onWillShowOverlay = + new Emitter(); + readonly onWillShowOverlay: Event = + this._onWillShowOverlay.event; + + get element(): HTMLElement { + return this._element; + } + + get panels(): string[] { + return this.tabs.map((_) => _.value.panel.id); + } + + get size(): number { + return this.tabs.length; + } + + constructor( + private readonly group: DockviewGroupPanel, + private readonly accessor: DockviewComponent + ) { + super(); + + this._element = document.createElement('div'); + this._element.className = 'dv-tabs-panel'; + this._element.style.display = 'flex'; + this._element.style.overflow = 'auto'; + this._tabsList = document.createElement('div'); + this._tabsList.className = 'dv-tabs-container'; + this._element.appendChild(this._tabsList); + + const observer = new OverflowObserver(this._tabsList); + + this.addDisposables( + observer, + observer.onDidChange((event) => { + const hasOverflow = event.hasScrollX || event.hasScrollY; + if (this._hasOverflow !== hasOverflow) { + this.toggleDropdown(hasOverflow); + } + }), + addDisposableListener(this.element, 'pointerdown', (event) => { + if (event.defaultPrevented) { + return; + } + + const isLeftClick = event.button === 0; + + if (isLeftClick) { + this.accessor.doSetGroupActive(this.group); + } + }), + Disposable.from(() => { + for (const { value, disposable } of this.tabs) { + disposable.dispose(); + value.dispose(); + } + + this.tabs = []; + }) + ); + } + + indexOf(id: string): number { + return this.tabs.findIndex((tab) => tab.value.panel.id === id); + } + + isActive(tab: Tab): boolean { + return ( + this.selectedIndex > -1 && + this.tabs[this.selectedIndex].value === tab + ); + } + + setActivePanel(panel: IDockviewPanel): void { + this.tabs.forEach((tab) => { + const isActivePanel = panel.id === tab.value.panel.id; + tab.value.setActive(isActivePanel); + }); + } + + openPanel(panel: IDockviewPanel, index: number = this.tabs.length): void { + if (this.tabs.find((tab) => tab.value.panel.id === panel.id)) { + return; + } + const tab = new Tab(panel, this.accessor, this.group); + tab.setContent(panel.view.tab); + + const disposable = new CompositeDisposable( + tab.onDragStart((event) => { + this._onTabDragStart.fire({ nativeEvent: event, panel }); + }), + tab.onChanged((event) => { + const isFloatingGroupsEnabled = + !this.accessor.options.disableFloatingGroups; + + const isFloatingWithOnePanel = + this.group.api.location.type === 'floating' && + this.size === 1; + + if ( + isFloatingGroupsEnabled && + !isFloatingWithOnePanel && + event.shiftKey + ) { + event.preventDefault(); + + const panel = this.accessor.getGroupPanel(tab.panel.id); + + const { top, left } = tab.element.getBoundingClientRect(); + const { top: rootTop, left: rootLeft } = + this.accessor.element.getBoundingClientRect(); + + this.accessor.addFloatingGroup(panel as DockviewPanel, { + x: left - rootLeft, + y: top - rootTop, + inDragMode: true, + }); + return; + } + + const isLeftClick = event.button === 0; + + if (!isLeftClick || event.defaultPrevented) { + return; + } + + if (this.group.activePanel !== panel) { + this.group.model.openPanel(panel); + } + }), + tab.onDrop((event) => { + this._onDrop.fire({ + event: event.nativeEvent, + index: this.tabs.findIndex((x) => x.value === tab), + }); + }), + tab.onWillShowOverlay((event) => { + this._onWillShowOverlay.fire( + new WillShowOverlayLocationEvent(event, { + kind: 'tab', + panel: this.group.activePanel, + api: this.accessor.api, + group: this.group, + getData: getPanelData, + }) + ); + }) + ); + + const value: IValueDisposable = { value: tab, disposable }; + + this.addTab(value, index); + } + + delete(id: string): void { + const index = this.indexOf(id); + const tabToRemove = this.tabs.splice(index, 1)[0]; + + const { value, disposable } = tabToRemove; + + disposable.dispose(); + value.dispose(); + value.element.remove(); + } + + private addTab( + tab: IValueDisposable, + index: number = this.tabs.length + ): void { + if (index < 0 || index > this.tabs.length) { + throw new Error('invalid location'); + } + + this._tabsList.insertBefore( + tab.value.element, + this._tabsList.children[index] + ); + + this.tabs = [ + ...this.tabs.slice(0, index), + tab, + ...this.tabs.slice(index), + ]; + + if (this.selectedIndex < 0) { + this.selectedIndex = index; + } + } + + private toggleDropdown(show: boolean): void { + this._hasOverflow = show; + if (this._dropdownAnchor) { + this._dropdownAnchor.remove(); + this._dropdownAnchor = null; + } + + if (!show) { + return; + } + + this._dropdownAnchor = document.createElement('div'); + this._dropdownAnchor.style.width = '10px'; + this._dropdownAnchor.style.height = '100%'; + this._dropdownAnchor.style.flexShrink = '0'; + this._dropdownAnchor.style.backgroundColor = 'red'; + + this.element.appendChild(this._dropdownAnchor); + + addDisposableListener(this._dropdownAnchor, 'click', (event) => { + const el = document.createElement('div'); + el.style.width = '200px'; + el.style.maxHeight = '600px'; + el.style.overflow = 'auto'; + el.style.backgroundColor = 'lightgreen'; + + this.tabs.map((tab) => { + const tab2 = new Tab( + tab.value.panel, + this.accessor, + this.group + ); + tab2.setContent(tab.value.panel.view.newTab); + el.appendChild(tab2.element); + }); + + this.accessor.popupService.openPopover(el, { + x: event.clientX, + y: event.clientY, + }); + }); + } +} diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss index fef520e03..14815f8bc 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss @@ -7,17 +7,17 @@ font-size: var(--dv-tabs-and-actions-container-font-size); &.dv-single-tab.dv-full-width-single-tab { - .dv-tabs-container { - flex-grow: 1; - - .dv-tab { + .dv-tabs-container { flex-grow: 1; - } - } - .dv-void-container { - flex-grow: 0; - } + .dv-tab { + flex-grow: 1; + } + } + + .dv-void-container { + flex-grow: 0; + } } .dv-void-container { @@ -25,47 +25,4 @@ flex-grow: 1; cursor: grab; } - - .dv-tabs-container { - display: flex; - overflow-x: overlay; - overflow-y: hidden; - - scrollbar-width: thin; // firefox - - &::-webkit-scrollbar { - height: 3px; - } - - /* Track */ - &::-webkit-scrollbar-track { - background: transparent; - } - - /* Handle */ - &::-webkit-scrollbar-thumb { - background: var(--dv-tabs-container-scrollbar-color); - } - - .dv-tab { - -webkit-user-drag: element; - outline: none; - min-width: 75px; - cursor: pointer; - position: relative; - box-sizing: border-box; - - &:not(:first-child)::before { - content: ' '; - position: absolute; - top: 0; - left: 0; - z-index: 5; - pointer-events: none; - background-color: var(--dv-tab-divider-color); - width: 1px; - height: 100%; - } - } - } } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index d3bd0568b..41c1e5be8 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -1,17 +1,14 @@ -import { - IDisposable, - CompositeDisposable, - IValueDisposable, -} from '../../../lifecycle'; +import { IDisposable, CompositeDisposable } from '../../../lifecycle'; import { addDisposableListener, Emitter, Event } from '../../../events'; import { Tab } from '../tab/tab'; import { DockviewGroupPanel } from '../../dockviewGroupPanel'; import { VoidContainer } from './voidContainer'; import { toggleClass } from '../../../dom'; -import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel'; +import { IDockviewPanel } from '../../dockviewPanel'; import { DockviewComponent } from '../../dockviewComponent'; import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel'; import { getPanelData } from '../../../dnd/dataTransfer'; +import { Tabs } from './tabs'; export interface TabDropIndexEvent { readonly event: DragEvent; @@ -56,14 +53,12 @@ export class TabsContainer implements ITabsContainer { private readonly _element: HTMLElement; - private readonly tabContainer: HTMLElement; + private readonly tabs: Tabs; private readonly rightActionsContainer: HTMLElement; private readonly leftActionsContainer: HTMLElement; private readonly preActionsContainer: HTMLElement; private readonly voidContainer: VoidContainer; - private tabs: IValueDisposable[] = []; - private selectedIndex = -1; private rightActions: HTMLElement | undefined; private leftActions: HTMLElement | undefined; private preActions: HTMLElement | undefined; @@ -73,8 +68,9 @@ export class TabsContainer private readonly _onDrop = new Emitter(); readonly onDrop: Event = this._onDrop.event; - private readonly _onTabDragStart = new Emitter(); - readonly onTabDragStart: Event = this._onTabDragStart.event; + get onTabDragStart(): Event { + return this.tabs.onTabDragStart; + } private readonly _onGroupDragStart = new Emitter(); readonly onGroupDragStart: Event = @@ -86,11 +82,11 @@ export class TabsContainer this._onWillShowOverlay.event; get panels(): string[] { - return this.tabs.map((_) => _.value.panel.id); + return this.tabs.panels; } get size(): number { - return this.tabs.length; + return this.tabs.size; } get hidden(): boolean { @@ -102,6 +98,102 @@ export class TabsContainer this.element.style.display = value ? 'none' : ''; } + get element(): HTMLElement { + return this._element; + } + + constructor( + private readonly accessor: DockviewComponent, + private readonly group: DockviewGroupPanel + ) { + super(); + + this._element = document.createElement('div'); + this._element.className = 'dv-tabs-and-actions-container'; + + toggleClass( + this._element, + 'dv-full-width-single-tab', + this.accessor.options.singleTabMode === 'fullwidth' + ); + + this.rightActionsContainer = document.createElement('div'); + this.rightActionsContainer.className = 'dv-right-actions-container'; + + this.leftActionsContainer = document.createElement('div'); + this.leftActionsContainer.className = 'dv-left-actions-container'; + + this.preActionsContainer = document.createElement('div'); + this.preActionsContainer.className = 'dv-pre-actions-container'; + + this.tabs = new Tabs(group, accessor); + + this.voidContainer = new VoidContainer(this.accessor, this.group); + + this._element.appendChild(this.preActionsContainer); + this._element.appendChild(this.tabs.element); + this._element.appendChild(this.leftActionsContainer); + this._element.appendChild(this.voidContainer.element); + this._element.appendChild(this.rightActionsContainer); + + this.addDisposables( + this._onWillShowOverlay, + this._onDrop, + this._onGroupDragStart, + this.voidContainer, + this.voidContainer.onDragStart((event) => { + this._onGroupDragStart.fire({ + nativeEvent: event, + group: this.group, + }); + }), + this.voidContainer.onDrop((event) => { + this._onDrop.fire({ + event: event.nativeEvent, + index: this.tabs.size, + }); + }), + this.voidContainer.onWillShowOverlay((event) => { + this._onWillShowOverlay.fire( + new WillShowOverlayLocationEvent(event, { + kind: 'header_space', + panel: this.group.activePanel, + api: this.accessor.api, + group: this.group, + getData: getPanelData, + }) + ); + }), + addDisposableListener( + this.voidContainer.element, + 'pointerdown', + (event) => { + const isFloatingGroupsEnabled = + !this.accessor.options.disableFloatingGroups; + + if ( + isFloatingGroupsEnabled && + event.shiftKey && + this.group.api.location.type !== 'floating' + ) { + event.preventDefault(); + + const { top, left } = + this.element.getBoundingClientRect(); + const { top: rootTop, left: rootLeft } = + this.accessor.element.getBoundingClientRect(); + + this.accessor.addFloatingGroup(this.group, { + x: left - rootLeft + 20, + y: top - rootTop + 20, + inDragMode: true, + }); + } + } + ) + ); + } + show(): void { if (!this.hidden) { this.element.style.display = ''; @@ -154,269 +246,36 @@ export class TabsContainer } } - get element(): HTMLElement { - return this._element; + isActive(tab: Tab): boolean { + return this.tabs.isActive(tab); } - public isActive(tab: Tab): boolean { - return ( - this.selectedIndex > -1 && - this.tabs[this.selectedIndex].value === tab - ); + indexOf(id: string): number { + return this.tabs.indexOf(id); } - public indexOf(id: string): number { - return this.tabs.findIndex((tab) => tab.value.panel.id === id); - } - - constructor( - private readonly accessor: DockviewComponent, - private readonly group: DockviewGroupPanel - ) { - super(); - - this._element = document.createElement('div'); - this._element.className = 'dv-tabs-and-actions-container'; - - toggleClass( - this._element, - 'dv-full-width-single-tab', - this.accessor.options.singleTabMode === 'fullwidth' - ); - - this.rightActionsContainer = document.createElement('div'); - this.rightActionsContainer.className = 'dv-right-actions-container'; - - this.leftActionsContainer = document.createElement('div'); - this.leftActionsContainer.className = 'dv-left-actions-container'; - - this.preActionsContainer = document.createElement('div'); - this.preActionsContainer.className = 'dv-pre-actions-container'; - - this.tabContainer = document.createElement('div'); - this.tabContainer.className = 'dv-tabs-container'; - - this.voidContainer = new VoidContainer(this.accessor, this.group); - - this._element.appendChild(this.preActionsContainer); - this._element.appendChild(this.tabContainer); - this._element.appendChild(this.leftActionsContainer); - this._element.appendChild(this.voidContainer.element); - this._element.appendChild(this.rightActionsContainer); - - this.addDisposables( - this._onWillShowOverlay, - this._onDrop, - this._onTabDragStart, - this._onGroupDragStart, - this.voidContainer, - this.voidContainer.onDragStart((event) => { - this._onGroupDragStart.fire({ - nativeEvent: event, - group: this.group, - }); - }), - this.voidContainer.onDrop((event) => { - this._onDrop.fire({ - event: event.nativeEvent, - index: this.tabs.length, - }); - }), - this.voidContainer.onWillShowOverlay((event) => { - this._onWillShowOverlay.fire( - new WillShowOverlayLocationEvent(event, { - kind: 'header_space', - panel: this.group.activePanel, - api: this.accessor.api, - group: this.group, - getData: getPanelData, - }) - ); - }), - addDisposableListener( - this.voidContainer.element, - 'pointerdown', - (event) => { - const isFloatingGroupsEnabled = - !this.accessor.options.disableFloatingGroups; - - if ( - isFloatingGroupsEnabled && - event.shiftKey && - this.group.api.location.type !== 'floating' - ) { - event.preventDefault(); - - const { top, left } = - this.element.getBoundingClientRect(); - const { top: rootTop, left: rootLeft } = - this.accessor.element.getBoundingClientRect(); - - this.accessor.addFloatingGroup(this.group, { - x: left - rootLeft + 20, - y: top - rootTop + 20, - inDragMode: true, - }); - } - } - ), - addDisposableListener(this.tabContainer, 'pointerdown', (event) => { - if (event.defaultPrevented) { - return; - } - - const isLeftClick = event.button === 0; - - if (isLeftClick) { - this.accessor.doSetGroupActive(this.group); - } - }) - ); - } - - public setActive(_isGroupActive: boolean) { + setActive(_isGroupActive: boolean) { // noop } - public delete(id: string): void { - const index = this.tabs.findIndex((tab) => tab.value.panel.id === id); - - const tabToRemove = this.tabs.splice(index, 1)[0]; - - const { value, disposable } = tabToRemove; - - disposable.dispose(); - value.dispose(); - value.element.remove(); - + delete(id: string): void { + this.tabs.delete(id); this.updateClassnames(); } - public setActivePanel(panel: IDockviewPanel): void { - this.tabs.forEach((tab) => { - const isActivePanel = panel.id === tab.value.panel.id; - tab.value.setActive(isActivePanel); - }); + setActivePanel(panel: IDockviewPanel): void { + this.tabs.setActivePanel(panel); } - public openPanel( - panel: IDockviewPanel, - index: number = this.tabs.length - ): void { - if (this.tabs.find((tab) => tab.value.panel.id === panel.id)) { - return; - } - const tab = new Tab(panel, this.accessor, this.group); - tab.setContent(panel.view.tab); - - const disposable = new CompositeDisposable( - tab.onDragStart((event) => { - this._onTabDragStart.fire({ nativeEvent: event, panel }); - }), - tab.onChanged((event) => { - const isFloatingGroupsEnabled = - !this.accessor.options.disableFloatingGroups; - - const isFloatingWithOnePanel = - this.group.api.location.type === 'floating' && - this.size === 1; - - if ( - isFloatingGroupsEnabled && - !isFloatingWithOnePanel && - event.shiftKey - ) { - event.preventDefault(); - - const panel = this.accessor.getGroupPanel(tab.panel.id); - - const { top, left } = tab.element.getBoundingClientRect(); - const { top: rootTop, left: rootLeft } = - this.accessor.element.getBoundingClientRect(); - - this.accessor.addFloatingGroup(panel as DockviewPanel, { - x: left - rootLeft, - y: top - rootTop, - inDragMode: true, - }); - return; - } - - const isLeftClick = event.button === 0; - - if (!isLeftClick || event.defaultPrevented) { - return; - } - - if (this.group.activePanel !== panel) { - this.group.model.openPanel(panel); - } - }), - tab.onDrop((event) => { - this._onDrop.fire({ - event: event.nativeEvent, - index: this.tabs.findIndex((x) => x.value === tab), - }); - }), - tab.onWillShowOverlay((event) => { - this._onWillShowOverlay.fire( - new WillShowOverlayLocationEvent(event, { - kind: 'tab', - panel: this.group.activePanel, - api: this.accessor.api, - group: this.group, - getData: getPanelData, - }) - ); - }) - ); - - const value: IValueDisposable = { value: tab, disposable }; - - this.addTab(value, index); + openPanel(panel: IDockviewPanel, index: number = this.tabs.size): void { + this.tabs.openPanel(panel, index); + this.updateClassnames(); } - public closePanel(panel: IDockviewPanel): void { + closePanel(panel: IDockviewPanel): void { this.delete(panel.id); } - public dispose(): void { - super.dispose(); - - for (const { value, disposable } of this.tabs) { - disposable.dispose(); - value.dispose(); - } - - this.tabs = []; - } - - private addTab( - tab: IValueDisposable, - index: number = this.tabs.length - ): void { - if (index < 0 || index > this.tabs.length) { - throw new Error('invalid location'); - } - - this.tabContainer.insertBefore( - tab.value.element, - this.tabContainer.children[index] - ); - - this.tabs = [ - ...this.tabs.slice(0, index), - tab, - ...this.tabs.slice(index), - ]; - - if (this.selectedIndex < 0) { - this.selectedIndex = index; - } - - this.updateClassnames(); - } - private updateClassnames(): void { toggleClass(this._element, 'dv-single-tab', this.size === 1); } diff --git a/packages/dockview-core/src/dockview/dockviewComponent.scss b/packages/dockview-core/src/dockview/dockviewComponent.scss index 386bf2a82..b08c0ada0 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.scss +++ b/packages/dockview-core/src/dockview/dockviewComponent.scss @@ -18,7 +18,10 @@ .dv-groupview { &.dv-active-group { - > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab { + > .dv-tabs-and-actions-container + > .dv-tabs-panel + > .dv-tabs-container + > .dv-tab { &.dv-active-tab { background-color: var( --dv-activegroup-visiblepanel-tab-background-color @@ -34,7 +37,10 @@ } } &.dv-inactive-group { - > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab { + > .dv-tabs-and-actions-container + > .dv-tabs-panel + > .dv-tabs-container + > .dv-tab { &.dv-active-tab { background-color: var( --dv-inactivegroup-visiblepanel-tab-background-color diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 63915751d..58618a025 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -74,6 +74,7 @@ import { } from '../overlay/overlayRenderContainer'; import { PopoutWindow } from '../popoutWindow'; import { StrictEventsSequencing } from './strictEventsSequencing'; +import { PopupService } from './components/popupService'; const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = { activationSize: { type: 'pixels', value: 10 }, @@ -256,6 +257,7 @@ export class DockviewComponent private watermark: IWatermarkRenderer | null = null; readonly overlayRenderContainer: OverlayRenderContainer; + readonly popupService: PopupService; private readonly _onWillDragPanel = new Emitter(); readonly onWillDragPanel: Event = this._onWillDragPanel.event; @@ -381,6 +383,8 @@ export class DockviewComponent className: options.className, }); + this.popupService = new PopupService(this.element); + this.overlayRenderContainer = new OverlayRenderContainer( this.gridview.element, this diff --git a/packages/dockview-core/src/dockview/dockviewPanelModel.ts b/packages/dockview-core/src/dockview/dockviewPanelModel.ts index 950b4d577..777717bad 100644 --- a/packages/dockview-core/src/dockview/dockviewPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanelModel.ts @@ -14,6 +14,7 @@ export interface IDockviewPanelModel extends IDisposable { readonly tabComponent?: string; readonly content: IContentRenderer; readonly tab: ITabRenderer; + readonly newTab: ITabRenderer; update(event: PanelUpdateEvent): void; layout(width: number, height: number): void; init(params: GroupPanelPartInitParameters): void; @@ -42,6 +43,11 @@ export class DockviewPanelModel implements IDockviewPanelModel { this._tab = this.createTabComponent(this.id, tabComponent); } + get newTab() { + const cmp = this.createTabComponent(this.id, this.tabComponent); + return cmp; + } + init(params: GroupPanelPartInitParameters): void { this.content.init(params); this.tab.init(params); From 2a3647db28db0db0ee44d0f55145236c8de32110 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Thu, 9 Jan 2025 20:16:59 +0000 Subject: [PATCH 41/83] chore: v3.0.1 docs --- packages/docs/blog/2025-01-09-dockview-3.0.1.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/docs/blog/2025-01-09-dockview-3.0.1.md diff --git a/packages/docs/blog/2025-01-09-dockview-3.0.1.md b/packages/docs/blog/2025-01-09-dockview-3.0.1.md new file mode 100644 index 000000000..bb1afb917 --- /dev/null +++ b/packages/docs/blog/2025-01-09-dockview-3.0.1.md @@ -0,0 +1,17 @@ +--- +slug: dockview-3.0.-release +title: Dockview 3.0.1 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +## 🔥 Breaking changes + +- Fix duplicate HTML element [#810](https://github.com/mathuo/dockview/issues/818) From 872ec7cba9fa8c99f9dd932b5d1f384fca7d6778 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Thu, 9 Jan 2025 20:17:19 +0000 Subject: [PATCH 42/83] chore(release): publish v3.0.1 --- lerna.json | 2 +- packages/dockview-angular/package.json | 4 ++-- packages/dockview-core/package.json | 2 +- packages/dockview-react/package.json | 4 ++-- packages/dockview-vue/package.json | 4 ++-- packages/dockview/package.json | 4 ++-- packages/docs/package.json | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lerna.json b/lerna.json index 8e89ed7f6..97e950503 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "3.0.0", + "version": "3.0.1", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-angular/package.json b/packages/dockview-angular/package.json index 410f007d1..8d8f75a20 100644 --- a/packages/dockview-angular/package.json +++ b/packages/dockview-angular/package.json @@ -1,6 +1,6 @@ { "name": "dockview-angular", - "version": "3.0.0", + "version": "3.0.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^3.0.0" + "dockview-core": "^3.0.1" } } diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index ac9d66d86..10c792a06 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "3.0.0", + "version": "3.0.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", diff --git a/packages/dockview-react/package.json b/packages/dockview-react/package.json index 34ef63def..496eef5ec 100644 --- a/packages/dockview-react/package.json +++ b/packages/dockview-react/package.json @@ -1,6 +1,6 @@ { "name": "dockview-react", - "version": "3.0.0", + "version": "3.0.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage" }, "dependencies": { - "dockview": "^3.0.0" + "dockview": "^3.0.1" } } diff --git a/packages/dockview-vue/package.json b/packages/dockview-vue/package.json index 97697e7b9..a8e0ccd5a 100644 --- a/packages/dockview-vue/package.json +++ b/packages/dockview-vue/package.json @@ -1,6 +1,6 @@ { "name": "dockview-vue", - "version": "3.0.0", + "version": "3.0.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -52,7 +52,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage" }, "dependencies": { - "dockview-core": "^3.0.0" + "dockview-core": "^3.0.1" }, "peerDependencies": { "vue": "^3.4.0" diff --git a/packages/dockview/package.json b/packages/dockview/package.json index aa5fb113b..7fded5fbb 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "3.0.0", + "version": "3.0.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,7 +54,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^3.0.0" + "dockview-core": "^3.0.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/packages/docs/package.json b/packages/docs/package.json index 700febd27..81ac3b853 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "3.0.0", + "version": "3.0.1", "private": true, "scripts": { "build": "npm run build-templates && docusaurus build", @@ -38,7 +38,7 @@ "ag-grid-react": "^31.0.2", "axios": "^1.6.3", "clsx": "^2.1.0", - "dockview": "^3.0.0", + "dockview": "^3.0.1", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", "react-laag": "^2.0.5", From c6865c691ca5d01589785861caa8877c7928c7ef Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Thu, 9 Jan 2025 20:38:42 +0000 Subject: [PATCH 43/83] bug: fix floating->popout->floating group transition --- .../dockview/dockviewComponent.spec.ts | 72 +++++++++++++++++++ .../src/dockview/dockviewComponent.ts | 14 ++++ 2 files changed, 86 insertions(+) diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 0be413221..0d00d1a31 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -5021,6 +5021,78 @@ describe('dockviewComponent', () => { expect(panel3.api.location.type).toBe('grid'); }); + test('grid -> floating -> popout -> floating', async () => { + const container = document.createElement('div'); + + window.open = () => setupMockWindow(); + + const dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + dockview.layout(1000, 500); + + const panel1 = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + + const panel2 = dockview.addPanel({ + id: 'panel_2', + component: 'default', + }); + + const panel3 = dockview.addPanel({ + id: 'panel_3', + component: 'default', + position: { direction: 'right' }, + }); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('grid'); + expect(panel3.api.location.type).toBe('grid'); + + dockview.addFloatingGroup(panel2.group); + + expect(panel1.api.location.type).toBe('floating'); + expect(panel2.api.location.type).toBe('floating'); + expect(panel3.api.location.type).toBe('grid'); + + await dockview.addPopoutGroup(panel2.group); + + expect(panel1.api.location.type).toBe('popout'); + expect(panel2.api.location.type).toBe('popout'); + expect(panel3.api.location.type).toBe('grid'); + + dockview.addFloatingGroup(panel2.group); + + expect(panel1.api.location.type).toBe('floating'); + expect(panel2.api.location.type).toBe('floating'); + expect(panel3.api.location.type).toBe('grid'); + + await dockview.addPopoutGroup(panel2.group); + + expect(panel1.api.location.type).toBe('popout'); + expect(panel2.api.location.type).toBe('popout'); + expect(panel3.api.location.type).toBe('grid'); + + panel2.group.api.moveTo({ group: panel3.group }); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('grid'); + expect(panel3.api.location.type).toBe('grid'); + }); + test('that panel is rendered when moving from popout to new group', async () => { const container = document.createElement('div'); diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 63915751d..f7875d919 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -846,6 +846,20 @@ export class DockviewComponent this.overlayRenderContainer; returnedGroup = group; + const alreadyRemoved = !this._popoutGroups.find( + (p) => p.popoutGroup === group + ); + + if (alreadyRemoved) { + /** + * If this popout group was explicitly removed then we shouldn't run the additional + * steps. To tell if the running of this disposable is the result of this popout group + * being explicitly removed we can check if this popout group is still referenced in + * the `this._popoutGroups` list. + */ + return; + } + if (floatingBox) { this.addFloatingGroup(group, { height: floatingBox.height, From 1a3c6ea7db37b8b2d621c5fba6f2d5b7f6cc6ce9 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Fri, 10 Jan 2025 20:55:25 +0000 Subject: [PATCH 44/83] bug: duplicate container HTML Element --- .../gridview/baseComponentGridview.spec.ts | 15 +++++ .../paneview/paneviewComponent.spec.ts | 59 ++++++++----------- .../splitview/splitviewComponent.spec.ts | 59 ++++++++----------- .../src/dockview/dockviewComponent.ts | 4 +- .../src/gridview/baseComponentGridview.ts | 7 ++- .../src/gridview/gridviewComponent.ts | 4 +- .../src/paneview/paneviewComponent.ts | 9 ++- .../dockview-core/src/splitview/splitview.ts | 4 +- .../src/splitview/splitviewComponent.ts | 12 ++-- packages/dockview/src/dockview/dockview.tsx | 6 +- packages/dockview/src/gridview/gridview.tsx | 6 +- packages/dockview/src/paneview/paneview.tsx | 6 +- packages/dockview/src/splitview/splitview.tsx | 6 +- 13 files changed, 91 insertions(+), 106 deletions(-) diff --git a/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts b/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts index d8ed45209..3ab49faa7 100644 --- a/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts +++ b/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts @@ -105,6 +105,21 @@ class ClassUnderTest extends BaseGrid { } describe('baseComponentGridview', () => { + test('that the container is not removed when grid is disposed', () => { + const root = document.createElement('div'); + const container = document.createElement('div'); + root.appendChild(container); + + const cut = new ClassUnderTest(container, { + orientation: Orientation.HORIZONTAL, + proportionalLayout: true, + }); + + cut.dispose(); + + expect(container.parentElement).toBe(root); + }); + test('that .layout(...) force flag works', () => { const cut = new ClassUnderTest(document.createElement('div'), { orientation: Orientation.HORIZONTAL, diff --git a/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts b/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts index 77a58712b..8cfc7fc2e 100644 --- a/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts @@ -67,6 +67,27 @@ describe('componentPaneview', () => { container.className = 'container'; }); + test('that the container is not removed when grid is disposed', () => { + const root = document.createElement('div'); + const container = document.createElement('div'); + root.appendChild(container); + + const paneview = new PaneviewComponent(container, { + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, + }); + + paneview.dispose(); + + expect(container.parentElement).toBe(root); + }); + test('vertical panels', () => { const disposables = new CompositeDisposable(); @@ -293,40 +314,6 @@ describe('componentPaneview', () => { disposable.dispose(); }); - test('dispose of paneviewComponent', () => { - expect(container.childNodes.length).toBe(0); - - const paneview = new PaneviewComponent(container, { - createComponent: (options) => { - switch (options.name) { - case 'default': - return new TestPanel(options.id, options.name); - default: - throw new Error('unsupported'); - } - }, - }); - - paneview.layout(1000, 1000); - - paneview.addPanel({ - id: 'panel1', - component: 'default', - title: 'Panel 1', - }); - paneview.addPanel({ - id: 'panel2', - component: 'default', - title: 'Panel 2', - }); - - expect(container.childNodes.length).toBeGreaterThan(0); - - paneview.dispose(); - - expect(container.childNodes.length).toBe(0); - }); - test('panel is disposed of when component is disposed', () => { const paneview = new PaneviewComponent(container, { createComponent: (options) => { @@ -606,10 +593,10 @@ describe('componentPaneview', () => { className: 'test-a test-b', }); - expect(paneview.element.className).toBe('container test-a test-b'); + expect(paneview.element.className).toBe('test-a test-b'); paneview.updateOptions({ className: 'test-b test-c' }); - expect(paneview.element.className).toBe('container test-b test-c'); + expect(paneview.element.className).toBe('test-b test-c'); }); }); diff --git a/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts b/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts index 6ab1415b2..90d0b3986 100644 --- a/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts @@ -26,6 +26,28 @@ describe('componentSplitview', () => { container.className = 'container'; }); + test('that the container is not removed when grid is disposed', () => { + const root = document.createElement('div'); + const container = document.createElement('div'); + root.appendChild(container); + + const splitview = new SplitviewComponent(container, { + orientation: Orientation.VERTICAL, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, + }); + + splitview.dispose(); + + expect(container.parentElement).toBe(root); + }); + test('event leakage', () => { Emitter.setLeakageMonitorEnabled(true); @@ -451,39 +473,6 @@ describe('componentSplitview', () => { disposable.dispose(); }); - test('dispose of splitviewComponent', () => { - expect(container.childNodes.length).toBe(0); - - const splitview = new SplitviewComponent(container, { - orientation: Orientation.HORIZONTAL, - createComponent: (options) => { - switch (options.name) { - case 'default': - return new TestPanel(options.id, options.name); - default: - throw new Error('unsupported'); - } - }, - }); - - splitview.layout(1000, 1000); - - splitview.addPanel({ - id: 'panel1', - component: 'default', - }); - splitview.addPanel({ - id: 'panel2', - component: 'default', - }); - - expect(container.childNodes.length).toBeGreaterThan(0); - - splitview.dispose(); - - expect(container.childNodes.length).toBe(0); - }); - test('panel is disposed of when component is disposed', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, @@ -736,10 +725,10 @@ describe('componentSplitview', () => { className: 'test-a test-b', }); - expect(splitview.element.className).toBe('container test-a test-b'); + expect(splitview.element.className).toBe('test-a test-b'); splitview.updateOptions({ className: 'test-b test-c' }); - expect(splitview.element.className).toBe('container test-b test-c'); + expect(splitview.element.className).toBe('test-b test-c'); }); }); diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 63915751d..305b73536 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -368,8 +368,8 @@ export class DockviewComponent return this._floatingGroups; } - constructor(parentElement: HTMLElement, options: DockviewComponentOptions) { - super(parentElement, { + constructor(container: HTMLElement, options: DockviewComponentOptions) { + super(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, styles: options.hideBorders diff --git a/packages/dockview-core/src/gridview/baseComponentGridview.ts b/packages/dockview-core/src/gridview/baseComponentGridview.ts index 537435310..93779f5a0 100644 --- a/packages/dockview-core/src/gridview/baseComponentGridview.ts +++ b/packages/dockview-core/src/gridview/baseComponentGridview.ts @@ -155,14 +155,17 @@ export abstract class BaseGrid this.gridview.locked = value; } - constructor(parentElement: HTMLElement, options: BaseGridOptions) { - super(parentElement, options.disableAutoResizing); + constructor(container: HTMLElement, options: BaseGridOptions) { + super(document.createElement('div'), options.disableAutoResizing); this.element.style.height = '100%'; this.element.style.width = '100%'; this._classNames = new Classnames(this.element); this._classNames.setClassNames(options.className ?? ''); + // the container is owned by the third-party, do not modify/delete it + container.appendChild(this.element); + this.gridview = new Gridview( !!options.proportionalLayout, options.styles, diff --git a/packages/dockview-core/src/gridview/gridviewComponent.ts b/packages/dockview-core/src/gridview/gridviewComponent.ts index 00084ce44..80df754bd 100644 --- a/packages/dockview-core/src/gridview/gridviewComponent.ts +++ b/packages/dockview-core/src/gridview/gridviewComponent.ts @@ -113,8 +113,8 @@ export class GridviewComponent this._deserializer = value; } - constructor(parentElement: HTMLElement, options: GridviewComponentOptions) { - super(parentElement, { + constructor(container: HTMLElement, options: GridviewComponentOptions) { + super(container, { proportionalLayout: options.proportionalLayout ?? true, orientation: options.orientation, styles: options.hideBorders diff --git a/packages/dockview-core/src/paneview/paneviewComponent.ts b/packages/dockview-core/src/paneview/paneviewComponent.ts index 86c87a690..b47ca0181 100644 --- a/packages/dockview-core/src/paneview/paneviewComponent.ts +++ b/packages/dockview-core/src/paneview/paneviewComponent.ts @@ -195,8 +195,10 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { return this._options; } - constructor(parentElement: HTMLElement, options: PaneviewComponentOptions) { - super(parentElement, options.disableAutoResizing); + constructor(container: HTMLElement, options: PaneviewComponentOptions) { + super(document.createElement('div'), options.disableAutoResizing); + this.element.style.height = '100%'; + this.element.style.width = '100%'; this.addDisposables( this._onDidLayoutChange, @@ -210,6 +212,9 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { this._classNames = new Classnames(this.element); this._classNames.setClassNames(options.className ?? ''); + // the container is owned by the third-party, do not modify/delete it + container.appendChild(this.element); + this._options = options; this.paneview = new Paneview(this.element, { diff --git a/packages/dockview-core/src/splitview/splitview.ts b/packages/dockview-core/src/splitview/splitview.ts index fc5857d08..77fbb9cf5 100644 --- a/packages/dockview-core/src/splitview/splitview.ts +++ b/packages/dockview-core/src/splitview/splitview.ts @@ -32,7 +32,7 @@ export interface ISplitviewStyles { } export interface SplitViewOptions { - orientation: Orientation; + orientation?: Orientation; descriptor?: ISplitViewDescriptor; proportionalLayout?: boolean; styles?: ISplitviewStyles; @@ -225,7 +225,7 @@ export class Splitview { private readonly container: HTMLElement, options: SplitViewOptions ) { - this._orientation = options.orientation; + this._orientation = options.orientation ?? Orientation.VERTICAL; this.element = this.createContainer(); this.margin = options.margin ?? 0; diff --git a/packages/dockview-core/src/splitview/splitviewComponent.ts b/packages/dockview-core/src/splitview/splitviewComponent.ts index 5e988c422..a165ba4b3 100644 --- a/packages/dockview-core/src/splitview/splitviewComponent.ts +++ b/packages/dockview-core/src/splitview/splitviewComponent.ts @@ -155,15 +155,17 @@ export class SplitviewComponent : this.splitview.orthogonalSize; } - constructor( - parentElement: HTMLElement, - options: SplitviewComponentOptions - ) { - super(parentElement, options.disableAutoResizing); + constructor(container: HTMLElement, options: SplitviewComponentOptions) { + super(document.createElement('div'), options.disableAutoResizing); + this.element.style.height = '100%'; + this.element.style.width = '100%'; this._classNames = new Classnames(this.element); this._classNames.setClassNames(options.className ?? ''); + // the container is owned by the third-party, do not modify/delete it + container.appendChild(this.element); + this._options = options; this.splitview = new Splitview(this.element, options); diff --git a/packages/dockview/src/dockview/dockview.tsx b/packages/dockview/src/dockview/dockview.tsx index fefc605f1..2e4e2fc57 100644 --- a/packages/dockview/src/dockview/dockview.tsx +++ b/packages/dockview/src/dockview/dockview.tsx @@ -318,11 +318,7 @@ export const DockviewReact = React.forwardRef( }, [props.prefixHeaderActionsComponent]); return ( -
+
{portals}
); diff --git a/packages/dockview/src/gridview/gridview.tsx b/packages/dockview/src/gridview/gridview.tsx index 60b172651..9bc9dd60b 100644 --- a/packages/dockview/src/gridview/gridview.tsx +++ b/packages/dockview/src/gridview/gridview.tsx @@ -126,11 +126,7 @@ export const GridviewReact = React.forwardRef( }, [props.components]); return ( -
+
{portals}
); diff --git a/packages/dockview/src/paneview/paneview.tsx b/packages/dockview/src/paneview/paneview.tsx index 34ab300b9..e0d49d98d 100644 --- a/packages/dockview/src/paneview/paneview.tsx +++ b/packages/dockview/src/paneview/paneview.tsx @@ -176,11 +176,7 @@ export const PaneviewReact = React.forwardRef( }, [props.onDidDrop]); return ( -
+
{portals}
); diff --git a/packages/dockview/src/splitview/splitview.tsx b/packages/dockview/src/splitview/splitview.tsx index 63d2cf5d9..bb8593381 100644 --- a/packages/dockview/src/splitview/splitview.tsx +++ b/packages/dockview/src/splitview/splitview.tsx @@ -126,11 +126,7 @@ export const SplitviewReact = React.forwardRef( }, [props.components]); return ( -
+
{portals}
); From c122bfd310c21491fddcf3ebfd33ce2fabaa5610 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Fri, 10 Jan 2025 22:09:24 +0000 Subject: [PATCH 45/83] fixup demo wrt React.StrictMode --- .../react/dockview/demo-dockview/src/app.tsx | 149 ++++++++++-------- .../demo-dockview/src/groupActions.tsx | 2 - packages/docs/src/theme/Root.tsx | 6 +- 3 files changed, 88 insertions(+), 69 deletions(-) diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx index a9f4ea97c..896e3968d 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx @@ -176,75 +176,92 @@ const DockviewDemo = (props: { theme?: string }) => { setPending([]); }, [pending]); - const onReady = (event: DockviewReadyEvent) => { - setApi(event.api); - - event.api.onDidAddPanel((event) => { - setPanels((_) => [..._, event.id]); - addLogLine(`Panel Added ${event.id}`); - }); - event.api.onDidActivePanelChange((event) => { - setActivePanel(event?.id); - addLogLine(`Panel Activated ${event?.id}`); - }); - event.api.onDidRemovePanel((event) => { - setPanels((_) => { - const next = [..._]; - next.splice( - next.findIndex((x) => x === event.id), - 1 - ); - - return next; - }); - addLogLine(`Panel Removed ${event.id}`); - }); - - event.api.onDidAddGroup((event) => { - setGroups((_) => [..._, event.id]); - addLogLine(`Group Added ${event.id}`); - }); - - event.api.onDidMovePanel((event) => { - addLogLine(`Panel Moved ${event.panel.id}`); - }); - - event.api.onDidMaximizedGroupChange((event) => { - addLogLine( - `Group Maximized Changed ${event.group.api.id} [${event.isMaximized}]` - ); - }); - - event.api.onDidRemoveGroup((event) => { - setGroups((_) => { - const next = [..._]; - next.splice( - next.findIndex((x) => x === event.id), - 1 - ); - - return next; - }); - addLogLine(`Group Removed ${event.id}`); - }); - - event.api.onDidActiveGroupChange((event) => { - setActiveGroup(event?.id); - addLogLine(`Group Activated ${event?.id}`); - }); - - const state = localStorage.getItem('dv-demo-state'); - if (state) { - try { - event.api.fromJSON(JSON.parse(state)); - return; - } catch { - localStorage.removeItem('dv-demo-state'); - } + React.useEffect(() => { + if (!api) { return; } - defaultConfig(event.api); + const disposables = [ + api.onDidAddPanel((event) => { + setPanels((_) => [..._, event.id]); + addLogLine(`Panel Added ${event.id}`); + }), + api.onDidActivePanelChange((event) => { + setActivePanel(event?.id); + addLogLine(`Panel Activated ${event?.id}`); + }), + api.onDidRemovePanel((event) => { + setPanels((_) => { + const next = [..._]; + next.splice( + next.findIndex((x) => x === event.id), + 1 + ); + + return next; + }); + addLogLine(`Panel Removed ${event.id}`); + }), + + api.onDidAddGroup((event) => { + setGroups((_) => [..._, event.id]); + addLogLine(`Group Added ${event.id}`); + }), + + api.onDidMovePanel((event) => { + addLogLine(`Panel Moved ${event.panel.id}`); + }), + + api.onDidMaximizedGroupChange((event) => { + addLogLine( + `Group Maximized Changed ${event.group.api.id} [${event.isMaximized}]` + ); + }), + + api.onDidRemoveGroup((event) => { + setGroups((_) => { + const next = [..._]; + next.splice( + next.findIndex((x) => x === event.id), + 1 + ); + + return next; + }); + addLogLine(`Group Removed ${event.id}`); + }), + + api.onDidActiveGroupChange((event) => { + setActiveGroup(event?.id); + addLogLine(`Group Activated ${event?.id}`); + }), + ]; + + const loadLayout = () => { + const state = localStorage.getItem('dv-demo-state'); + + if (state) { + try { + api.fromJSON(JSON.parse(state)); + return; + } catch { + localStorage.removeItem('dv-demo-state'); + } + return; + } + + defaultConfig(api); + }; + + loadLayout(); + + return () => { + disposables.forEach((disposable) => disposable.dispose()); + }; + }, [api]); + + const onReady = (event: DockviewReadyEvent) => { + setApi(event.api); }; const [watermark, setWatermark] = React.useState(false); diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/groupActions.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/groupActions.tsx index 815621b72..0af2a66cc 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/groupActions.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/groupActions.tsx @@ -88,7 +88,6 @@ const GroupAction = (props: { } onClick={() => { if (group) { - props.api.addFloatingGroup(group, { width: 400, height: 300, @@ -99,7 +98,6 @@ const GroupAction = (props: { right: 50, }, }); - } }} > diff --git a/packages/docs/src/theme/Root.tsx b/packages/docs/src/theme/Root.tsx index 516775b04..eb8c4705e 100644 --- a/packages/docs/src/theme/Root.tsx +++ b/packages/docs/src/theme/Root.tsx @@ -3,5 +3,9 @@ import { RecoilRoot } from 'recoil'; // Default implementation, that you can customize export default function Root({ children }) { - return {children}; + return ( + + {children} + + ); } From 239c99ffe536ad5f8454e234d23c8fb7aa3bc63c Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 11 Jan 2025 14:58:06 +0000 Subject: [PATCH 46/83] chore: fix docs --- packages/docs/src/pages/demo.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs/src/pages/demo.tsx b/packages/docs/src/pages/demo.tsx index ad48bf86c..cab33d7ef 100644 --- a/packages/docs/src/pages/demo.tsx +++ b/packages/docs/src/pages/demo.tsx @@ -27,7 +27,7 @@ const ThemeToggle: React.FC = () => { value={theme} > {themeConfig.map((theme) => { - return ; + return ; })}
From b46a473cdf41972995feba60b7fce926ee81de5b Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 11 Jan 2025 15:00:00 +0000 Subject: [PATCH 47/83] chore: v3.0.2 release notes --- .../docs/blog/2025-01-09-dockview-3.0.1.md | 2 +- .../docs/blog/2025-01-11-dockview-3.0.2.md | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 packages/docs/blog/2025-01-11-dockview-3.0.2.md diff --git a/packages/docs/blog/2025-01-09-dockview-3.0.1.md b/packages/docs/blog/2025-01-09-dockview-3.0.1.md index bb1afb917..cef014f95 100644 --- a/packages/docs/blog/2025-01-09-dockview-3.0.1.md +++ b/packages/docs/blog/2025-01-09-dockview-3.0.1.md @@ -1,5 +1,5 @@ --- -slug: dockview-3.0.-release +slug: dockview-3.0.1-release title: Dockview 3.0.1 tags: [release] --- diff --git a/packages/docs/blog/2025-01-11-dockview-3.0.2.md b/packages/docs/blog/2025-01-11-dockview-3.0.2.md new file mode 100644 index 000000000..f223b81bd --- /dev/null +++ b/packages/docs/blog/2025-01-11-dockview-3.0.2.md @@ -0,0 +1,18 @@ +--- +slug: dockview-3.0.2-release +title: Dockview 3.0.2 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +## 🔥 Breaking changes + +- Fix issue when transitioning panel from floating to popout to floating [#810](https://github.com/mathuo/dockview/issues/824) +- Fix duplicate HTML containers [#825](https://github.com/mathuo/dockview/pull/825) From c0cbe8983592bc76b6549edd8409b6e3f3b54049 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 11 Jan 2025 15:12:57 +0000 Subject: [PATCH 48/83] chore(release): publish v3.0.2 --- lerna.json | 2 +- packages/dockview-angular/package.json | 4 ++-- packages/dockview-core/package.json | 2 +- packages/dockview-react/package.json | 4 ++-- packages/dockview-vue/package.json | 4 ++-- packages/dockview/package.json | 4 ++-- packages/docs/package.json | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lerna.json b/lerna.json index 97e950503..ea2983497 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "3.0.1", + "version": "3.0.2", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-angular/package.json b/packages/dockview-angular/package.json index 8d8f75a20..90dec64e4 100644 --- a/packages/dockview-angular/package.json +++ b/packages/dockview-angular/package.json @@ -1,6 +1,6 @@ { "name": "dockview-angular", - "version": "3.0.1", + "version": "3.0.2", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^3.0.1" + "dockview-core": "^3.0.2" } } diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index 10c792a06..46d930d42 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "3.0.1", + "version": "3.0.2", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", diff --git a/packages/dockview-react/package.json b/packages/dockview-react/package.json index 496eef5ec..172398f3b 100644 --- a/packages/dockview-react/package.json +++ b/packages/dockview-react/package.json @@ -1,6 +1,6 @@ { "name": "dockview-react", - "version": "3.0.1", + "version": "3.0.2", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage" }, "dependencies": { - "dockview": "^3.0.1" + "dockview": "^3.0.2" } } diff --git a/packages/dockview-vue/package.json b/packages/dockview-vue/package.json index a8e0ccd5a..694edfad0 100644 --- a/packages/dockview-vue/package.json +++ b/packages/dockview-vue/package.json @@ -1,6 +1,6 @@ { "name": "dockview-vue", - "version": "3.0.1", + "version": "3.0.2", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -52,7 +52,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage" }, "dependencies": { - "dockview-core": "^3.0.1" + "dockview-core": "^3.0.2" }, "peerDependencies": { "vue": "^3.4.0" diff --git a/packages/dockview/package.json b/packages/dockview/package.json index 7fded5fbb..e95bbedcf 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "3.0.1", + "version": "3.0.2", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,7 +54,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^3.0.1" + "dockview-core": "^3.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/packages/docs/package.json b/packages/docs/package.json index 81ac3b853..563eaddac 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "3.0.1", + "version": "3.0.2", "private": true, "scripts": { "build": "npm run build-templates && docusaurus build", @@ -38,7 +38,7 @@ "ag-grid-react": "^31.0.2", "axios": "^1.6.3", "clsx": "^2.1.0", - "dockview": "^3.0.1", + "dockview": "^3.0.2", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", "react-laag": "^2.0.5", From 6b75b5093c0a0eded3a5df3077c914f1aaf52199 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 11 Jan 2025 21:04:44 +0000 Subject: [PATCH 49/83] chore: fix docs --- packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx index 896e3968d..e1ccaef0f 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx @@ -376,7 +376,6 @@ const DockviewDemo = (props: { theme?: string }) => { style={{ flexGrow: 1, overflow: 'hidden', - height: '100%', display: 'flex', }} > From 6421a726539876438bd214903cfebc6e5afc4dd0 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 14 Jan 2025 18:01:59 +0000 Subject: [PATCH 50/83] chore: use dockview-core package metrics in README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 980350c04..a0a3a2b1f 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,12 @@ --- -[![npm version](https://badge.fury.io/js/dockview.svg)](https://www.npmjs.com/package/dockview) -[![npm](https://img.shields.io/npm/dm/dockview)](https://www.npmjs.com/package/dockview) +[![npm version](https://badge.fury.io/js/dockview-core.svg)](https://www.npmjs.com/package/dockview-core) +[![npm](https://img.shields.io/npm/dm/dockview-core)](https://www.npmjs.com/package/dockview-core) [![CI Build](https://github.com/mathuo/dockview/workflows/CI/badge.svg)](https://github.com/mathuo/dockview/actions?query=workflow%3ACI) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=coverage)](https://sonarcloud.io/summary/overall?id=mathuo_dockview) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=alert_status)](https://sonarcloud.io/summary/overall?id=mathuo_dockview) -[![Bundle Phobia](https://badgen.net/bundlephobia/minzip/dockview)](https://bundlephobia.com/result?p=dockview) +[![Bundle Phobia](https://badgen.net/bundlephobia/minzip/dockview-core)](https://bundlephobia.com/result?p=dockview-core) ## From 5ca5ffac8dfe65b7e48c703483eaddb7da593f2b Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 19 Jan 2025 20:00:26 +0000 Subject: [PATCH 51/83] tmp --- .../dockview/components/titlebar/tabs.scss | 31 +++++++++++++++++++ .../src/dockview/components/titlebar/tabs.tsx | 30 +++++++++--------- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss index 01b715404..62f600047 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss @@ -39,4 +39,35 @@ height: 100%; } } + + &.dv-tabs-overflow-container { + flex-direction: column; + height: unset; + + .dv-tab { + height: var(--dv-tabs-and-actions-container-height); + } + + .dv-active-tab { + background-color: var( + --dv-activegroup-visiblepanel-tab-background-color + ); + color: var(--dv-activegroup-visiblepanel-tab-color); + } + .dv-inactive-tab { + background-color: var( + --dv-activegroup-hiddenpanel-tab-background-color + ); + color: var(--dv-activegroup-hiddenpanel-tab-color); + } + } +} + +.dv-tabs-panel { + .dv-tabs-overflow-handle { + height: 100%; + width: 15px; + flex-shrink: 0; + background-color: red; + } } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.tsx b/packages/dockview-core/src/dockview/components/titlebar/tabs.tsx index 0a86dd54b..a477531a4 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.tsx +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.tsx @@ -220,6 +220,7 @@ export class Tabs extends CompositeDisposable { private toggleDropdown(show: boolean): void { this._hasOverflow = show; + if (this._dropdownAnchor) { this._dropdownAnchor.remove(); this._dropdownAnchor = null; @@ -230,28 +231,29 @@ export class Tabs extends CompositeDisposable { } this._dropdownAnchor = document.createElement('div'); - this._dropdownAnchor.style.width = '10px'; - this._dropdownAnchor.style.height = '100%'; - this._dropdownAnchor.style.flexShrink = '0'; - this._dropdownAnchor.style.backgroundColor = 'red'; + this._dropdownAnchor.className = 'dv-tabs-overflow-handle'; this.element.appendChild(this._dropdownAnchor); addDisposableListener(this._dropdownAnchor, 'click', (event) => { const el = document.createElement('div'); - el.style.width = '200px'; - el.style.maxHeight = '600px'; el.style.overflow = 'auto'; - el.style.backgroundColor = 'lightgreen'; + el.className = + 'dv-tabs-and-actions-container dv-tabs-container dv-tabs-overflow-container'; this.tabs.map((tab) => { - const tab2 = new Tab( - tab.value.panel, - this.accessor, - this.group - ); - tab2.setContent(tab.value.panel.view.newTab); - el.appendChild(tab2.element); + const child = tab.value.element.cloneNode(true); + + const wrapper = document.createElement('div'); + + wrapper.addEventListener('mousedown', () => { + this.accessor.popupService.close(); + tab.value.element.scrollIntoView(); + tab.value.panel.api.setActive(); + }); + wrapper.appendChild(child); + + el.appendChild(wrapper); }); this.accessor.popupService.openPopover(el, { From dc6bfc67e32d702947315a8990e7f4b79c1a0dbf Mon Sep 17 00:00:00 2001 From: Milo Mighdoll <14153763+milomg@users.noreply.github.com> Date: Mon, 20 Jan 2025 12:42:31 -0500 Subject: [PATCH 52/83] Fix vertical sash in replit theme --- packages/dockview-core/src/theme.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dockview-core/src/theme.scss b/packages/dockview-core/src/theme.scss index 85aa66280..5eb3f7442 100644 --- a/packages/dockview-core/src/theme.scss +++ b/packages/dockview-core/src/theme.scss @@ -267,7 +267,7 @@ } } - .vertical > .sash-container > .sash { + .dv-vertical > .dv-sash-container > .dv-sash { &:not(.disabled) { &::after { content: ''; From ca09ae537d926f060d084df62ac8e8e98a22d66e Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:35:43 +0000 Subject: [PATCH 53/83] Create SECURITY.md --- SECURITY.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..d04d9fe59 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,8 @@ +# Reporting a Vulnerability + +- Dockview is an entirely open source project. +- All build and publication scripts use public Github Action files found [here](https://github.com/mathuo/dockview/tree/master/.github/workflows). +- All npm publications are verified through the use of [provenance statements](https://docs.npmjs.com/generating-provenance-statements/). +- All builds are scanned with SonarCube and outputs can be found [here](https://sonarcloud.io/summary/overall?id=mathuo_dockview). + +If you believe you have found a security or vulnerability issue please send a complete example to github.mathuo@gmail.com where it will be investigated. From 3e409c426500deeb93fecd555524e71454cec5ab Mon Sep 17 00:00:00 2001 From: Chris Nolan Date: Tue, 28 Jan 2025 08:15:19 +0000 Subject: [PATCH 54/83] check tabToRemove is not undefined before access --- .../src/dockview/components/titlebar/tabsContainer.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index d3bd0568b..bebf652ee 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -283,6 +283,9 @@ export class TabsContainer const tabToRemove = this.tabs.splice(index, 1)[0]; + if (!tabToRemove) + return; + const { value, disposable } = tabToRemove; disposable.dispose(); From c6dcef537f4ac4861f23c17f82e8b4cbaed51784 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:47:53 +0000 Subject: [PATCH 55/83] bug: setup onDidAcitvePanelChange subscription quicker --- .../dockview/dockviewGroupPanel.spec.ts | 48 ++++++++++++++++++- .../src/api/dockviewGroupPanelApi.ts | 24 +--------- .../src/dockview/dockviewGroupPanel.ts | 6 +++ 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts index bbebcfb89..eb2ab5320 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts @@ -2,10 +2,12 @@ import { DockviewComponent } from '../../dockview/dockviewComponent'; import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; import { fromPartial } from '@total-typescript/shoehorn'; import { GroupOptions } from '../../dockview/dockviewGroupPanelModel'; -import { DockviewPanel } from '../../dockview/dockviewPanel'; +import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel'; import { DockviewPanelModelMock } from '../__mocks__/mockDockviewPanelModel'; import { IContentRenderer, ITabRenderer } from '../../dockview/types'; import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer'; +import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel'; +import { ContentContainer } from '../../dockview/components/panel/content'; describe('dockviewGroupPanel', () => { test('default minimum/maximium width/height', () => { @@ -24,6 +26,50 @@ describe('dockviewGroupPanel', () => { expect(cut.maximumWidth).toBe(Number.MAX_SAFE_INTEGER); }); + test('that onDidActivePanelChange is configured at inline', () => { + const accessor = fromPartial({ + onDidActivePanelChange: jest.fn(), + onDidAddPanel: jest.fn(), + onDidRemovePanel: jest.fn(), + options: {}, + api: {}, + renderer: 'always', + overlayRenderContainer: { + attach: jest.fn(), + detatch: jest.fn(), + }, + doSetGroupActive: jest.fn(), + }); + const options = fromPartial({}); + + const cut = new DockviewGroupPanel(accessor, 'test_id', options); + + let counter = 0; + + cut.api.onDidActivePanelChange((event) => { + counter++; + }); + + cut.model.openPanel( + fromPartial({ + updateParentGroup: jest.fn(), + view: { + tab: { element: document.createElement('div') }, + content: new ContentContainer(accessor, cut.model), + }, + api: { + renderer: 'onlyWhenVisible', + onDidTitleChange: jest.fn(), + onDidParametersChange: jest.fn(), + }, + layout: jest.fn(), + runEvents: jest.fn(), + }) + ); + + expect(counter).toBe(1); + }); + test('group constraints', () => { const accessor = fromPartial({ onDidActivePanelChange: jest.fn(), diff --git a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts index 12424debb..73b5a461b 100644 --- a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts @@ -6,7 +6,6 @@ import { DockviewGroupLocation, } from '../dockview/dockviewGroupPanelModel'; import { Emitter, Event } from '../events'; -import { MutableDisposable } from '../lifecycle'; import { GridviewPanelApi, GridviewPanelApiImpl } from './gridviewPanelApi'; export interface DockviewGroupMoveParams { @@ -41,8 +40,6 @@ const NOT_INITIALIZED_MESSAGE = 'dockview: DockviewGroupPanelApiImpl not initialized'; export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { - private readonly _mutableDisposable = new MutableDisposable(); - private _group: DockviewGroupPanel | undefined; readonly _onDidLocationChange = @@ -50,8 +47,7 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { readonly onDidLocationChange: Event = this._onDidLocationChange.event; - private readonly _onDidActivePanelChange = - new Emitter(); + readonly _onDidActivePanelChange = new Emitter(); readonly onDidActivePanelChange = this._onDidActivePanelChange.event; get location(): DockviewGroupLocation { @@ -66,8 +62,7 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { this.addDisposables( this._onDidLocationChange, - this._onDidActivePanelChange, - this._mutableDisposable + this._onDidActivePanelChange ); } @@ -140,21 +135,6 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { } initialize(group: DockviewGroupPanel): void { - /** - * TODO: Annoying initialization order caveat, find a better way to initialize and avoid needing null checks - * - * Due to the order on initialization we know that the model isn't defined until later in the same stack-frame of setup. - * By queuing a microtask we can ensure the setup is completed within the same stack-frame, but after everything else has - * finished ensuring the `model` is defined. - */ - this._group = group; - - queueMicrotask(() => { - this._mutableDisposable.value = - this._group!.model.onDidActivePanelChange((event) => { - this._onDidActivePanelChange.fire(event); - }); - }); } } diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanel.ts index a3e714829..fd5e70fe3 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanel.ts @@ -124,6 +124,12 @@ export class DockviewGroupPanel options, this ); + + this.addDisposables( + this.model.onDidActivePanelChange((event) => { + this.api._onDidActivePanelChange.fire(event); + }) + ); } override focus(): void { From 0e9c648fa9c7391f346cfa09e6f2205bb416bb57 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Thu, 30 Jan 2025 13:43:53 +0000 Subject: [PATCH 56/83] feat: close tab with middle btn --- .../src/dockview/components/tab/defaultTab.ts | 6 --- .../components/titlebar/tabsContainer.ts | 21 +++++--- .../__tests__/dockview/defaultTab.spec.tsx | 52 +++++++------------ packages/dockview/src/dockview/defaultTab.tsx | 16 ------ 4 files changed, 31 insertions(+), 64 deletions(-) diff --git a/packages/dockview-core/src/dockview/components/tab/defaultTab.ts b/packages/dockview-core/src/dockview/components/tab/defaultTab.ts index 80c30be5d..205e4e562 100644 --- a/packages/dockview-core/src/dockview/components/tab/defaultTab.ts +++ b/packages/dockview-core/src/dockview/components/tab/defaultTab.ts @@ -29,12 +29,6 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer { this._element.appendChild(this._content); this._element.appendChild(this.action); - this.addDisposables( - addDisposableListener(this.action, 'pointerdown', (ev) => { - ev.preventDefault(); - }) - ); - this.render(); } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index d3bd0568b..f697c4083 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -314,6 +314,10 @@ export class TabsContainer this._onTabDragStart.fire({ nativeEvent: event, panel }); }), tab.onChanged((event) => { + if (event.defaultPrevented) { + return; + } + const isFloatingGroupsEnabled = !this.accessor.options.disableFloatingGroups; @@ -342,14 +346,15 @@ export class TabsContainer return; } - const isLeftClick = event.button === 0; - - if (!isLeftClick || event.defaultPrevented) { - return; - } - - if (this.group.activePanel !== panel) { - this.group.model.openPanel(panel); + switch (event.button) { + case 0: // left click or touch + if (this.group.activePanel !== panel) { + this.group.model.openPanel(panel); + } + break; + case 1: // middle click + panel.api.close(); + break; } }), tab.onDrop((event) => { diff --git a/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx b/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx index 427e9650e..93da0a000 100644 --- a/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx +++ b/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx @@ -10,7 +10,9 @@ import { Disposable } from 'dockview-core/dist/cjs/lifecycle'; describe('defaultTab', () => { test('has close button by default', async () => { const api = fromPartial({ - onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + onDidTitleChange: jest + .fn() + .mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; @@ -30,7 +32,9 @@ describe('defaultTab', () => { test('that title is displayed', async () => { const api = fromPartial({ title: 'test_title', - onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + onDidTitleChange: jest + .fn() + .mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; @@ -84,7 +88,9 @@ describe('defaultTab', () => { test('has no close button when hideClose=true', async () => { const api = fromPartial({ - onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + onDidTitleChange: jest + .fn() + .mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; @@ -105,7 +111,9 @@ describe('defaultTab', () => { test('that settings closeActionOverride skips api.close()', async () => { const api = fromPartial({ close: jest.fn(), - onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + onDidTitleChange: jest + .fn() + .mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; @@ -134,7 +142,9 @@ describe('defaultTab', () => { test('that clicking close calls api.close()', async () => { const api = fromPartial({ close: jest.fn(), - onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + onDidTitleChange: jest + .fn() + .mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; @@ -158,7 +168,9 @@ describe('defaultTab', () => { test('has close button when hideClose=false', async () => { const api = fromPartial({ - onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + onDidTitleChange: jest + .fn() + .mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; @@ -175,32 +187,4 @@ describe('defaultTab', () => { const element = await screen.getByTestId('dockview-dv-default-tab'); expect(element.querySelector('.dv-default-tab-action')).toBeTruthy(); }); - - test('that pointerDown on close button prevents panel becoming active', async () => { - const api = fromPartial({ - setActive: jest.fn(), - onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), - }); - const containerApi = fromPartial({}); - const params = {}; - - render( - - ); - - const element = await screen.getByTestId('dockview-dv-default-tab'); - const btn = element.querySelector( - '.dv-default-tab-action' - ) as HTMLElement; - - fireEvent.pointerDown(btn); - expect(api.setActive).toHaveBeenCalledTimes(0); - - fireEvent.click(element); - expect(api.setActive).toHaveBeenCalledTimes(1); - }); }); diff --git a/packages/dockview/src/dockview/defaultTab.tsx b/packages/dockview/src/dockview/defaultTab.tsx index f1586fa83..d2801f40e 100644 --- a/packages/dockview/src/dockview/defaultTab.tsx +++ b/packages/dockview/src/dockview/defaultTab.tsx @@ -53,26 +53,10 @@ export const DockviewDefaultTab: React.FunctionComponent< e.preventDefault(); }, []); - const onClick = React.useCallback( - (event: React.MouseEvent) => { - if (event.defaultPrevented) { - return; - } - - api.setActive(); - - if (rest.onClick) { - rest.onClick(event); - } - }, - [api, rest.onClick] - ); - return (
{title} From 19a22c49c37cc814c167b4d052541b71797d0234 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Thu, 30 Jan 2025 20:40:18 +0000 Subject: [PATCH 57/83] bug: popup disposal runs after instance dispose --- .../dockview/dockviewComponent.spec.ts | 75 ++++++++++++++++++- .../components/titlebar/tabsContainer.ts | 5 +- .../src/dockview/dockviewComponent.ts | 4 + 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 0d00d1a31..695a30ada 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -1102,7 +1102,9 @@ describe('dockviewComponent', () => { disposable.dispose(); }); - test('events flow', () => { + test('events flow', async () => { + window.open = () => setupMockWindow(); + dockview.layout(1000, 1000); let events: { @@ -1295,7 +1297,42 @@ describe('dockviewComponent', () => { expect(dockview.size).toBe(0); expect(dockview.totalPanels).toBe(0); + events = []; + + const panel8 = dockview.addPanel({ + id: 'panel8', + component: 'default', + }); + const panel9 = dockview.addPanel({ + id: 'panel9', + component: 'default', + floating: true, + }); + const panel10 = dockview.addPanel({ + id: 'panel10', + component: 'default', + }); + + expect(await dockview.addPopoutGroup(panel10)).toBeTruthy(); + + expect(events).toEqual([ + { type: 'ADD_GROUP', group: panel8.group }, + { type: 'ADD_PANEL', panel: panel8 }, + { type: 'ACTIVE_GROUP', group: panel8.group }, + { type: 'ACTIVE_PANEL', panel: panel8 }, + { type: 'ADD_GROUP', group: panel9.group }, + { type: 'ADD_PANEL', panel: panel9 }, + { type: 'ACTIVE_GROUP', group: panel9.group }, + { type: 'ACTIVE_PANEL', panel: panel9 }, + { type: 'ADD_PANEL', panel: panel10 }, + { type: 'ACTIVE_PANEL', panel: panel10 }, + { type: 'ADD_GROUP', group: panel10.group }, + ]); + + events = []; disposable.dispose(); + + expect(events.length).toBe(0); }); test('that removing a panel from a group reflects in the dockviewcomponent when searching for a panel', () => { @@ -5696,6 +5733,42 @@ describe('dockviewComponent', () => { }, ]); }); + + test('dispose of dockview instance when popup is open', async () => { + const container = document.createElement('div'); + + window.open = () => setupMockWindow(); + + const dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + dockview.layout(1000, 500); + + dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + + const panel2 = dockview.addPanel({ + id: 'panel_2', + component: 'default', + }); + + expect(await dockview.addPopoutGroup(panel2.group)).toBeTruthy(); + + dockview.dispose(); + }); }); describe('maximized group', () => { diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index bebf652ee..40bf65a93 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -283,8 +283,9 @@ export class TabsContainer const tabToRemove = this.tabs.splice(index, 1)[0]; - if (!tabToRemove) - return; + if (!tabToRemove) { + throw new Error(`dockview: Tab not found`); + } const { value, disposable } = tabToRemove; diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 0dde93454..20343ae6c 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -821,6 +821,10 @@ export class DockviewComponent ), overlayRenderContainer, Disposable.from(() => { + if (this.isDisposed) { + return; // cleanup may run after instance is disposed + } + if ( isGroupAddedToDom && this.getPanel(referenceGroup.id) From 71b99541b3b9ca1f4c48bf365716940d4555f56b Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:57:55 +0000 Subject: [PATCH 58/83] chore: v3.0.3 docs --- .../docs/blog/2025-02-02-dockview-3.0.3.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 packages/docs/blog/2025-02-02-dockview-3.0.3.md diff --git a/packages/docs/blog/2025-02-02-dockview-3.0.3.md b/packages/docs/blog/2025-02-02-dockview-3.0.3.md new file mode 100644 index 000000000..5e8b63497 --- /dev/null +++ b/packages/docs/blog/2025-02-02-dockview-3.0.3.md @@ -0,0 +1,22 @@ +--- +slug: dockview-3.0.3-release +title: Dockview 3.0.3 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +- Close tab with middle mouse button [#847](https://github.com/mathuo/dockview/pull/847) + +## 🛠 Miscs + +- Bug: Fix crash on navigation with open popout group [#835](https://github.com/mathuo/dockview/pull/848) [#845](https://github.com/mathuo/dockview/pull/845) +- Bug: Subscribe to `onDidAcitvePanelChange` immediately, rather than deferred to `queueMicrotask` [#843](https://github.com/mathuo/dockview/pull/843) +- Bug: Minor theme fixup [#831](https://github.com/mathuo/dockview/pull/831) + +## 🔥 Breaking changes + From e71d8b1d093881fbe04a8c63497741771a502f57 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 2 Feb 2025 22:30:35 +0000 Subject: [PATCH 59/83] chore(release): publish v3.1.0 --- lerna.json | 2 +- packages/dockview-angular/package.json | 4 ++-- packages/dockview-core/package.json | 2 +- packages/dockview-react/package.json | 4 ++-- packages/dockview-vue/package.json | 4 ++-- packages/dockview/package.json | 4 ++-- packages/docs/package.json | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lerna.json b/lerna.json index ea2983497..d182f27dc 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "3.0.2", + "version": "3.1.0", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-angular/package.json b/packages/dockview-angular/package.json index 90dec64e4..3f440f8b0 100644 --- a/packages/dockview-angular/package.json +++ b/packages/dockview-angular/package.json @@ -1,6 +1,6 @@ { "name": "dockview-angular", - "version": "3.0.2", + "version": "3.1.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^3.0.2" + "dockview-core": "^3.1.0" } } diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index 46d930d42..2d73bd9e4 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "3.0.2", + "version": "3.1.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", diff --git a/packages/dockview-react/package.json b/packages/dockview-react/package.json index 172398f3b..1093f82f4 100644 --- a/packages/dockview-react/package.json +++ b/packages/dockview-react/package.json @@ -1,6 +1,6 @@ { "name": "dockview-react", - "version": "3.0.2", + "version": "3.1.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage" }, "dependencies": { - "dockview": "^3.0.2" + "dockview": "^3.1.0" } } diff --git a/packages/dockview-vue/package.json b/packages/dockview-vue/package.json index 694edfad0..a5e5ce109 100644 --- a/packages/dockview-vue/package.json +++ b/packages/dockview-vue/package.json @@ -1,6 +1,6 @@ { "name": "dockview-vue", - "version": "3.0.2", + "version": "3.1.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -52,7 +52,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage" }, "dependencies": { - "dockview-core": "^3.0.2" + "dockview-core": "^3.1.0" }, "peerDependencies": { "vue": "^3.4.0" diff --git a/packages/dockview/package.json b/packages/dockview/package.json index e95bbedcf..5b03985cf 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "3.0.2", + "version": "3.1.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,7 +54,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^3.0.2" + "dockview-core": "^3.1.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/packages/docs/package.json b/packages/docs/package.json index 563eaddac..4f474120a 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "3.0.2", + "version": "3.1.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": "^3.0.2", + "dockview": "^3.1.0", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", "react-laag": "^2.0.5", From a56f01215b35e8ef757e69d0e89274fd9451213b Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 2 Feb 2025 22:30:58 +0000 Subject: [PATCH 60/83] chore : v3.1.0 docs --- ...{2025-02-02-dockview-3.0.3.md => 2025-02-02-dockview-3.1.0.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/docs/blog/{2025-02-02-dockview-3.0.3.md => 2025-02-02-dockview-3.1.0.md} (100%) diff --git a/packages/docs/blog/2025-02-02-dockview-3.0.3.md b/packages/docs/blog/2025-02-02-dockview-3.1.0.md similarity index 100% rename from packages/docs/blog/2025-02-02-dockview-3.0.3.md rename to packages/docs/blog/2025-02-02-dockview-3.1.0.md From 8ed0fb5586bf196191ebb81e5367f4a6fdbfcbe5 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 2 Feb 2025 22:31:11 +0000 Subject: [PATCH 61/83] chore : v3.1.0 docs --- packages/docs/blog/2025-02-02-dockview-3.1.0.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/docs/blog/2025-02-02-dockview-3.1.0.md b/packages/docs/blog/2025-02-02-dockview-3.1.0.md index 5e8b63497..124100509 100644 --- a/packages/docs/blog/2025-02-02-dockview-3.1.0.md +++ b/packages/docs/blog/2025-02-02-dockview-3.1.0.md @@ -1,6 +1,6 @@ --- -slug: dockview-3.0.3-release -title: Dockview 3.0.3 +slug: dockview-3.1.0-release +title: Dockview 3.1.0 tags: [release] --- From d8da2f29c3ced8ebac24310f842553e1f01c2da7 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 8 Feb 2025 11:30:19 +0000 Subject: [PATCH 62/83] conditional middle-btn tab close --- .../src/dockview/components/tab/tab.ts | 12 ++---- .../components/titlebar/tabsContainer.ts | 5 +-- packages/dockview/src/dockview/defaultTab.tsx | 42 +++++++++++++++++-- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/packages/dockview-core/src/dockview/components/tab/tab.ts b/packages/dockview-core/src/dockview/components/tab/tab.ts index 1eb1174d8..0b6ad55dc 100644 --- a/packages/dockview-core/src/dockview/components/tab/tab.ts +++ b/packages/dockview-core/src/dockview/components/tab/tab.ts @@ -49,8 +49,8 @@ export class Tab extends CompositeDisposable { private readonly dropTarget: Droptarget; private content: ITabRenderer | undefined = undefined; - private readonly _onChanged = new Emitter(); - readonly onChanged: Event = this._onChanged.event; + private readonly _onPointDown = new Emitter(); + readonly onPointerDown: Event = this._onPointDown.event; private readonly _onDropped = new Emitter(); readonly onDrop: Event = this._onDropped.event; @@ -117,7 +117,7 @@ export class Tab extends CompositeDisposable { this.onWillShowOverlay = this.dropTarget.onWillShowOverlay; this.addDisposables( - this._onChanged, + this._onPointDown, this._onDropped, this._onDragStart, dragHandler.onDragStart((event) => { @@ -125,11 +125,7 @@ export class Tab extends CompositeDisposable { }), dragHandler, addDisposableListener(this._element, 'pointerdown', (event) => { - if (event.defaultPrevented) { - return; - } - - this._onChanged.fire(event); + this._onPointDown.fire(event); }), this.dropTarget.onDrop((event) => { this._onDropped.fire(event); diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index e420f1183..30a18af2d 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -317,7 +317,7 @@ export class TabsContainer tab.onDragStart((event) => { this._onTabDragStart.fire({ nativeEvent: event, panel }); }), - tab.onChanged((event) => { + tab.onPointerDown((event) => { if (event.defaultPrevented) { return; } @@ -356,9 +356,6 @@ export class TabsContainer this.group.model.openPanel(panel); } break; - case 1: // middle click - panel.api.close(); - break; } }), tab.onDrop((event) => { diff --git a/packages/dockview/src/dockview/defaultTab.tsx b/packages/dockview/src/dockview/defaultTab.tsx index d2801f40e..e12e61465 100644 --- a/packages/dockview/src/dockview/defaultTab.tsx +++ b/packages/dockview/src/dockview/defaultTab.tsx @@ -32,10 +32,15 @@ export const DockviewDefaultTab: React.FunctionComponent< params: _params, hideClose, closeActionOverride, + onPointerDown, + onPointerUp, + onPointerLeave, ...rest }) => { const title = useTitle(api); + const isMiddleMouseButton = React.useRef(false); + const onClose = React.useCallback( (event: React.MouseEvent) => { event.preventDefault(); @@ -49,21 +54,52 @@ export const DockviewDefaultTab: React.FunctionComponent< [api, closeActionOverride] ); - const onPointerDown = React.useCallback((e: React.MouseEvent) => { - e.preventDefault(); + const onBtnPointerDown = React.useCallback((event: React.MouseEvent) => { + event.preventDefault(); }, []); + const _onPointerDown = React.useCallback( + (event: React.PointerEvent) => { + isMiddleMouseButton.current = event.button === 1; + onPointerDown?.(event); + }, + [onPointerDown] + ); + + const _onPointerUp = React.useCallback( + (event: React.PointerEvent) => { + if (isMiddleMouseButton && event.button === 1 && !hideClose) { + isMiddleMouseButton.current = false; + onClose(event); + } + + onPointerUp?.(event); + }, + [onPointerUp, onClose, hideClose] + ); + + const _onPointerLeave = React.useCallback( + (event: React.PointerEvent) => { + isMiddleMouseButton.current = false; + onPointerLeave?.(event); + }, + [onPointerLeave] + ); + return (
{title} {!hideClose && (
From cdd0183fa9764a5ccc5b804bf6e92f07329d70e8 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 9 Feb 2025 14:40:29 +0000 Subject: [PATCH 63/83] chore: v3.1.1 docs --- .../docs/blog/2025-02-09-dockview-3.1.1.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 packages/docs/blog/2025-02-09-dockview-3.1.1.md diff --git a/packages/docs/blog/2025-02-09-dockview-3.1.1.md b/packages/docs/blog/2025-02-09-dockview-3.1.1.md new file mode 100644 index 000000000..925e98133 --- /dev/null +++ b/packages/docs/blog/2025-02-09-dockview-3.1.1.md @@ -0,0 +1,18 @@ +--- +slug: dockview-3.1.1-release +title: Dockview 3.1.1 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +- Bug: Fix Middle mouse button to close tab [#835](https://github.com/mathuo/dockview/issues/853) + +## 🔥 Breaking changes + From 16373429a83ddd575da54d33fa3adefa361652af Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 9 Feb 2025 14:40:58 +0000 Subject: [PATCH 64/83] chore(release): publish v3.1.1 --- lerna.json | 2 +- packages/dockview-angular/package.json | 4 ++-- packages/dockview-core/package.json | 2 +- packages/dockview-react/package.json | 4 ++-- packages/dockview-vue/package.json | 4 ++-- packages/dockview/package.json | 4 ++-- packages/docs/package.json | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lerna.json b/lerna.json index d182f27dc..ebba69a26 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "3.1.0", + "version": "3.1.1", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-angular/package.json b/packages/dockview-angular/package.json index 3f440f8b0..86b8d0ab0 100644 --- a/packages/dockview-angular/package.json +++ b/packages/dockview-angular/package.json @@ -1,6 +1,6 @@ { "name": "dockview-angular", - "version": "3.1.0", + "version": "3.1.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^3.1.0" + "dockview-core": "^3.1.1" } } diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index 2d73bd9e4..35d07101c 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "3.1.0", + "version": "3.1.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", diff --git a/packages/dockview-react/package.json b/packages/dockview-react/package.json index 1093f82f4..48ce159a3 100644 --- a/packages/dockview-react/package.json +++ b/packages/dockview-react/package.json @@ -1,6 +1,6 @@ { "name": "dockview-react", - "version": "3.1.0", + "version": "3.1.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage" }, "dependencies": { - "dockview": "^3.1.0" + "dockview": "^3.1.1" } } diff --git a/packages/dockview-vue/package.json b/packages/dockview-vue/package.json index a5e5ce109..e335e0fad 100644 --- a/packages/dockview-vue/package.json +++ b/packages/dockview-vue/package.json @@ -1,6 +1,6 @@ { "name": "dockview-vue", - "version": "3.1.0", + "version": "3.1.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -52,7 +52,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage" }, "dependencies": { - "dockview-core": "^3.1.0" + "dockview-core": "^3.1.1" }, "peerDependencies": { "vue": "^3.4.0" diff --git a/packages/dockview/package.json b/packages/dockview/package.json index 5b03985cf..1233e5765 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "3.1.0", + "version": "3.1.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,7 +54,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^3.1.0" + "dockview-core": "^3.1.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/packages/docs/package.json b/packages/docs/package.json index 4f474120a..845b3b75c 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "3.1.0", + "version": "3.1.1", "private": true, "scripts": { "build": "npm run build-templates && docusaurus build", @@ -38,7 +38,7 @@ "ag-grid-react": "^31.0.2", "axios": "^1.6.3", "clsx": "^2.1.0", - "dockview": "^3.1.0", + "dockview": "^3.1.1", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", "react-laag": "^2.0.5", From 490fc05baf45bd14a864c134c4d8471896aea007 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 9 Feb 2025 14:49:01 +0000 Subject: [PATCH 65/83] chore: add template example --- .../group-actions/typescript/src/index.css | 23 +++ .../group-actions/typescript/src/index.ts | 178 ++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 packages/docs/templates/dockview/group-actions/typescript/src/index.css create mode 100644 packages/docs/templates/dockview/group-actions/typescript/src/index.ts diff --git a/packages/docs/templates/dockview/group-actions/typescript/src/index.css b/packages/docs/templates/dockview/group-actions/typescript/src/index.css new file mode 100644 index 000000000..92ec05921 --- /dev/null +++ b/packages/docs/templates/dockview/group-actions/typescript/src/index.css @@ -0,0 +1,23 @@ +.dockview-groupcontrol-demo { + height: 100%; + display: flex; + align-items: center; + color: white; + background-color: black; + padding: 0px 8px; + + margin: 1px; + border: 1px dotted orange; + + +} + +.dockview-groupcontrol-demo .dockview-groupcontrol-demo-group-active { + padding: 0px 8px; +} + +.dockview-groupcontrol-demo .dockview-groupcontrol-demo-active-panel { + color: yellow; + padding: 0px 8px; +} + diff --git a/packages/docs/templates/dockview/group-actions/typescript/src/index.ts b/packages/docs/templates/dockview/group-actions/typescript/src/index.ts new file mode 100644 index 000000000..65fd84833 --- /dev/null +++ b/packages/docs/templates/dockview/group-actions/typescript/src/index.ts @@ -0,0 +1,178 @@ +import 'dockview-core/dist/styles/dockview.css'; +import { + createDockview, + DockviewGroupPanel, + GroupPanelPartInitParameters, + IContentRenderer, + IGroupHeaderProps, + IHeaderActionsRenderer, +} from 'dockview-core'; +import './index.css'; + +class Panel implements IContentRenderer { + private readonly _element: HTMLElement; + + get element(): HTMLElement { + return this._element; + } + + constructor() { + this._element = document.createElement('div'); + this._element.style.display = 'flex'; + this._element.style.justifyContent = 'center'; + this._element.style.alignItems = 'center'; + this._element.style.color = 'gray'; + this._element.style.height = '100%'; + } + + init(parameters: GroupPanelPartInitParameters): void { + // + } +} + +class PrefixHeader implements IHeaderActionsRenderer { + private readonly _element: HTMLElement; + + get element(): HTMLElement { + return this._element; + } + + constructor(group: DockviewGroupPanel) { + this._element = document.createElement('div'); + this._element.className = 'dockview-groupcontrol-demo'; + this._element.innerText = '🌲'; + } + + init(parameters: IGroupHeaderProps): void { + // + } + + dispose(): void { + // + } +} + +class RightHeaderActions implements IHeaderActionsRenderer { + private readonly _element: HTMLElement; + private readonly _disposables: (() => void)[] = []; + + get element(): HTMLElement { + return this._element; + } + + constructor(group: DockviewGroupPanel) { + this._element = document.createElement('div'); + this._element.className = 'dockview-groupcontrol-demo'; + } + + init(parameters: IGroupHeaderProps): void { + const group = parameters.group; + + const span = document.createElement('span'); + span.className = 'dockview-groupcontrol-demo-group-active'; + + this._element.appendChild(span); + + const d1 = group.api.onDidActiveChange(() => { + span.style.background = group.api.isActive ? 'green' : 'red'; + span.innerText = `${ + group.api.isActive ? 'Group Active' : 'Group Inactive' + }`; + }); + + span.style.background = group.api.isActive ? 'green' : 'red'; + span.innerText = `${ + group.api.isActive ? 'Group Active' : 'Group Inactive' + }`; + + this._disposables.push(() => d1.dispose()); + } + + dispose(): void { + this._disposables.forEach((dispose) => dispose()); + } +} + +class LeftHeaderActions implements IHeaderActionsRenderer { + private readonly _element: HTMLElement; + private readonly _disposables: (() => void)[] = []; + + get element(): HTMLElement { + return this._element; + } + + constructor(group: DockviewGroupPanel) { + console.log('group', group); + this._element = document.createElement('div'); + this._element.className = 'dockview-groupcontrol-demo'; + } + + init(parameters: IGroupHeaderProps): void { + const group = parameters.group; + + const span = document.createElement('span'); + span.className = 'dockview-groupcontrol-demo-active-panel'; + + this._element.appendChild(span); + + const d1 = group.api.onDidActivePanelChange((event) => { + console.log('event', event); + span.innerText = `activePanel: ${event.panel?.id || 'null'}`; + }); + + console.log('group.activePanel', group.activePanel); + + span.innerText = `activePanel: ${group.activePanel?.id || 'null'}`; + + this._disposables.push(() => d1.dispose()); + } + + dispose(): void { + this._disposables.forEach((dispose) => dispose()); + } +} + +const api = createDockview(document.getElementById('app'), { + className: 'dockview-theme-abyss', + createComponent: (options): IContentRenderer => { + switch (options.name) { + case 'default': + return new Panel(); + default: + throw new Error('Panel not found'); + } + }, + createPrefixHeaderActionComponent: (group): IHeaderActionsRenderer => { + return new PrefixHeader(group); + }, + createLeftHeaderActionComponent: (group): IHeaderActionsRenderer => { + return new LeftHeaderActions(group); + }, + createRightHeaderActionComponent: (group): IHeaderActionsRenderer => { + return new RightHeaderActions(group); + }, +}); + +api.addPanel({ + id: 'panel_1', + component: 'default', + title: 'Panel 1', +}); + +api.addPanel({ + id: 'panel_2', + component: 'default', + title: 'Panel 2', + position: { + direction: 'right', + }, +}); + +api.addPanel({ + id: 'panel_3', + component: 'default', + title: 'Panel 3', + position: { + direction: 'below', + }, +}); From f98be640ce5ae41b4246fb227137dbc1e1755e94 Mon Sep 17 00:00:00 2001 From: Amir Date: Wed, 12 Feb 2025 14:54:41 +0100 Subject: [PATCH 66/83] Added support for sash delay and duration customization --- packages/dockview-core/src/splitview/splitview.scss | 8 +++++--- packages/dockview-core/src/theme.scss | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/dockview-core/src/splitview/splitview.scss b/packages/dockview-core/src/splitview/splitview.scss index adf09b368..83aacddaa 100644 --- a/packages/dockview-core/src/splitview/splitview.scss +++ b/packages/dockview-core/src/splitview/splitview.scss @@ -118,14 +118,16 @@ touch-action: none; &:not(.disabled):active { - transition: background-color 0.1s ease-in-out; + transition: background-color + var(--dv-sash-transition-duration, 0.1s) ease-in-out; background-color: var(--dv-active-sash-color, transparent); } &:not(.disabled):hover { background-color: var(--dv-active-sash-color, transparent); - transition: background-color 0.1s ease-in-out; - transition-delay: 0.5s; + transition: background-color + var(--dv-sash-transition-duration, 0.1s) ease-in-out; + transition-delay: var(--dv-sash-transition-delay, 0s); } } } diff --git a/packages/dockview-core/src/theme.scss b/packages/dockview-core/src/theme.scss index 5eb3f7442..7b0e7a841 100644 --- a/packages/dockview-core/src/theme.scss +++ b/packages/dockview-core/src/theme.scss @@ -8,6 +8,9 @@ --dv-icon-hover-background-color: rgba(90, 93, 94, 0.31); --dv-floating-box-shadow: 8px 8px 8px 0px rgba(83, 89, 93, 0.5); --dv-overlay-z-index: 999; + + --dv-sash-transition-duration: 0.1s; + --dv-sash-transition-delay: 0.5s; } @mixin dockview-theme-dark-mixin { From 14b918b7ae5f2b9c21a706764d7389f3791b59bb Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:01:36 +0000 Subject: [PATCH 67/83] chore: enhance examples page --- packages/docs/docusaurus.config.js | 11 +------ packages/docs/scripts/buildTemplates.mjs | 10 +++++-- packages/docs/scripts/template.html | 37 +++++++++++++++++++++--- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/packages/docs/docusaurus.config.js b/packages/docs/docusaurus.config.js index 6a4bbd74c..97b77aeed 100644 --- a/packages/docs/docusaurus.config.js +++ b/packages/docs/docusaurus.config.js @@ -184,17 +184,8 @@ const config = { label: 'API', }, { to: '/blog', label: 'Blog', position: 'left' }, + { href: '/templates', target:"_blank", label: 'Examples', position: 'left' }, { to: '/demo', label: 'Demo', position: 'left' }, - // { - // to: 'https://dockview.dev/typedocs', - // label: 'TSDoc', - // position: 'left', - // }, - - // { - // type: 'docsVersionDropdown', - // position: 'right', - // }, { href: 'https://github.com/mathuo/dockview', position: 'right', diff --git a/packages/docs/scripts/buildTemplates.mjs b/packages/docs/scripts/buildTemplates.mjs index f2cc212ce..555d4fd2d 100644 --- a/packages/docs/scripts/buildTemplates.mjs +++ b/packages/docs/scripts/buildTemplates.mjs @@ -1,7 +1,6 @@ import fs from 'fs-extra'; import * as path from 'path'; import { argv } from 'process'; -import { execSync } from 'child_process'; import { fileURLToPath } from 'url'; @@ -86,7 +85,8 @@ function createIndexHTML(options) { .map(([key, value]) => `"${key}": "${value}"`) .join(',\n')}` ) - .replace('{{app}}', options.app); + .replace('{{app}}', options.app) + .replace('{{githubLink}}', options.githubUrl) } const input_dir = path.join(__dirname, '../templates'); @@ -97,6 +97,8 @@ const FRAMEWORKS = ['react', 'vue', 'typescript']; const list = []; +const githubUrl = "https://github.com/mathuo/dockview/tree/master/packages/docs/templates" + for (const component of COMPONENTS) { const componentDir = path.join(input_dir, component); @@ -115,6 +117,9 @@ for (const component of COMPONENTS) { path.join(componentDir, folder, framework, 'src'), path.join(output, component, folder, framework, 'src') ); + + const templateGithubUrl = `${githubUrl}/${component}/${folder}/${framework}/src` + const template = createIndexHTML({ title: `Dockview | ${folder} ${framework}`, app: @@ -127,6 +132,7 @@ for (const component of COMPONENTS) { USE_LOCAL_CDN ? 'local' : 'remote' ], }, + githubUrl: templateGithubUrl }); fs.writeFileSync( path.join(output, component, folder, framework, 'index.html'), diff --git a/packages/docs/scripts/template.html b/packages/docs/scripts/template.html index 56a5a9ebe..7dda559be 100644 --- a/packages/docs/scripts/template.html +++ b/packages/docs/scripts/template.html @@ -12,7 +12,7 @@ @@ -62,9 +82,18 @@ -
- + + Date: Thu, 30 Jan 2025 20:42:57 +0000 Subject: [PATCH 71/83] feat: improved dnd model --- .../src/__tests__/dnd/droptarget.spec.ts | 4 +- .../__tests__/dockview/components/tab.spec.ts | 51 ++-- .../components/titlebar/tabsContainer.spec.ts | 49 ++-- .../dockview/dockviewComponent.spec.ts | 40 +--- .../dockview/dockviewGroupPanelModel.spec.ts | 50 ++-- .../dockview-core/src/api/component.api.ts | 14 +- .../src/dnd/abstractDragHandler.ts | 6 +- .../src/dnd/dropTargetAnchorContainer.scss | 23 ++ .../src/dnd/dropTargetAnchorContainer.ts | 102 ++++++++ .../dockview-core/src/dnd/droptarget.scss | 10 +- packages/dockview-core/src/dnd/droptarget.ts | 204 +++++++++++++++- packages/dockview-core/src/dnd/ghost.ts | 5 +- .../dockview-core/src/dnd/groupDragHandler.ts | 4 +- .../src/dockview/components/panel/content.ts | 26 +- .../dockview/components/tab/defaultTab.scss | 4 +- .../src/dockview/components/tab/tab.ts | 32 ++- .../components/titlebar/tabsContainer.scss | 30 ++- .../components/titlebar/tabsContainer.ts | 5 +- .../components/titlebar/voidContainer.ts | 14 +- .../src/dockview/dockviewComponent.ts | 117 +++++++-- .../src/dockview/dockviewGroupPanelModel.ts | 37 +++ .../dockview-core/src/dockview/options.ts | 24 +- packages/dockview-core/src/dockview/theme.ts | 54 +++++ packages/dockview-core/src/index.ts | 1 + .../src/splitview/splitview.scss | 1 + .../dockview-core/src/splitview/splitview.ts | 2 + packages/dockview-core/src/theme.scss | 226 +++++++++++++----- .../src/theme/_drop-target-static-mixin.scss | 10 + .../src/theme/_sash-handle-mixin.scss | 53 ++++ .../dockview-core/src/theme/_space-mixin.scss | 52 ++++ packages/dockview/src/svg.tsx | 4 +- .../docs/docs/overview/getStarted/theme.mdx | 6 +- .../react/dockview/demo-dockview/src/app.scss | 1 + .../react/dockview/demo-dockview/src/app.tsx | 36 +-- .../dockview/demo-dockview/src/controls.tsx | 2 +- .../demo-dockview/src/gridActions.tsx | 40 +++- .../docs/src/components/frameworkSpecific.css | 27 ++- .../docs/src/components/frameworkSpecific.tsx | 5 +- .../src/components/ui/codeSandboxButton.scss | 2 +- .../src/components/ui/codeSandboxButton.tsx | 4 +- packages/docs/src/components/ui/container.tsx | 6 +- .../docs/src/components/ui/exampleFrame.tsx | 3 +- packages/docs/src/config/theme.config.ts | 33 ++- packages/docs/src/css/custom.scss | 28 ++- packages/docs/src/pages/demo.tsx | 135 ++++++++++- 45 files changed, 1232 insertions(+), 350 deletions(-) create mode 100644 packages/dockview-core/src/dnd/dropTargetAnchorContainer.scss create mode 100644 packages/dockview-core/src/dnd/dropTargetAnchorContainer.ts create mode 100644 packages/dockview-core/src/dockview/theme.ts create mode 100644 packages/dockview-core/src/theme/_drop-target-static-mixin.scss create mode 100644 packages/dockview-core/src/theme/_sash-handle-mixin.scss create mode 100644 packages/dockview-core/src/theme/_space-mixin.scss diff --git a/packages/dockview-core/src/__tests__/dnd/droptarget.spec.ts b/packages/dockview-core/src/__tests__/dnd/droptarget.spec.ts index 2d82095ab..b150ba896 100644 --- a/packages/dockview-core/src/__tests__/dnd/droptarget.spec.ts +++ b/packages/dockview-core/src/__tests__/dnd/droptarget.spec.ts @@ -16,10 +16,10 @@ describe('droptarget', () => { beforeEach(() => { element = document.createElement('div'); - jest.spyOn(element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 200); + jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 200); }); test('that dragover events are marked', () => { diff --git a/packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts index cb246b598..4a2f0c72d 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts @@ -8,6 +8,7 @@ import { DockviewGroupPanel } from '../../../dockview/dockviewGroupPanel'; import { DockviewGroupPanelModel } from '../../../dockview/dockviewGroupPanelModel'; import { Tab } from '../../../dockview/components/tab/tab'; import { IDockviewPanel } from '../../../dockview/dockviewPanel'; +import { fromPartial } from '@total-typescript/shoehorn'; describe('tab', () => { test('that empty tab has inactive-tab class', () => { @@ -46,15 +47,10 @@ describe('tab', () => { id: 'testcomponentid', }; }); - const groupviewMock = jest.fn, []>( - () => { - return { - canDisplayOverlay: jest.fn(), - }; - } - ); - const groupView = new groupviewMock() as DockviewGroupPanelModel; + const groupView = fromPartial({ + canDisplayOverlay: jest.fn(), + }); const groupPanelMock = jest.fn, []>(() => { return { @@ -72,38 +68,33 @@ describe('tab', () => { groupPanel ); - jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation( () => 100 ); fireEvent.dragEnter(cut.element); fireEvent.dragOver(cut.element); - expect(groupView.canDisplayOverlay).toBeCalled(); + expect(groupView.canDisplayOverlay).toHaveBeenCalled(); expect( cut.element.getElementsByClassName('dv-drop-target-dropzone').length ).toBe(0); }); - test('that if you drag over yourself no drop target is shown', () => { + test('that if you drag over yourself a drop target is shown', () => { const accessorMock = jest.fn, []>(() => { return { id: 'testcomponentid', }; }); - const groupviewMock = jest.fn, []>( - () => { - return { - canDisplayOverlay: jest.fn(), - }; - } - ); - const groupView = new groupviewMock() as DockviewGroupPanelModel; + const groupView = fromPartial({ + canDisplayOverlay: jest.fn(), + }); const groupPanelMock = jest.fn, []>(() => { return { @@ -121,10 +112,10 @@ describe('tab', () => { groupPanel ); - jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -136,11 +127,11 @@ describe('tab', () => { fireEvent.dragEnter(cut.element); fireEvent.dragOver(cut.element); - expect(groupView.canDisplayOverlay).toBeCalledTimes(0); + expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(0); expect( cut.element.getElementsByClassName('dv-drop-target-dropzone').length - ).toBe(0); + ).toBe(1); }); test('that if you drag over another tab a drop target is shown', () => { @@ -175,10 +166,10 @@ describe('tab', () => { groupPanel ); - jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -229,10 +220,10 @@ describe('tab', () => { groupPanel ); - jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -289,10 +280,10 @@ describe('tab', () => { groupPanel ); - jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation( () => 100 ); diff --git a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts index eee78a588..bae8daa8d 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts @@ -42,16 +42,16 @@ describe('tabsContainer', () => { const emptySpace = cut.element .getElementsByClassName('dv-void-container') - .item(0); + .item(0) as HTMLElement; if (!emptySpace!) { fail('element not found'); } - jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -73,15 +73,14 @@ describe('tabsContainer', () => { options: {}, }); - const groupviewMock = jest.fn, []>( - () => { - return { - canDisplayOverlay: jest.fn(), - }; - } - ); + const dropTargetContainer = document.createElement('div'); - const groupView = new groupviewMock() as DockviewGroupPanelModel; + const groupView = fromPartial({ + canDisplayOverlay: jest.fn(), + // dropTargetContainer: new DropTargetAnchorContainer( + // dropTargetContainer + // ), + }); const groupPanelMock = jest.fn, []>(() => { return { @@ -97,16 +96,16 @@ describe('tabsContainer', () => { const emptySpace = cut.element .getElementsByClassName('dv-void-container') - .item(0); + .item(0) as HTMLElement; if (!emptySpace!) { fail('element not found'); } - jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -129,6 +128,10 @@ describe('tabsContainer', () => { expect( cut.element.getElementsByClassName('dv-drop-target-dropzone').length ).toBe(1); + // expect( + // dropTargetContainer.getElementsByClassName('dv-drop-target-anchor') + // .length + // ).toBe(1); }); test('that dropping over the empty space should render a drop target', () => { @@ -166,16 +169,16 @@ describe('tabsContainer', () => { const emptySpace = cut.element .getElementsByClassName('dv-void-container') - .item(0); + .item(0) as HTMLElement; if (!emptySpace!) { fail('element not found'); } - jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -229,16 +232,16 @@ describe('tabsContainer', () => { const emptySpace = cut.element .getElementsByClassName('dv-void-container') - .item(0); + .item(0) as HTMLElement; if (!emptySpace!) { fail('element not found'); } - jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -291,16 +294,16 @@ describe('tabsContainer', () => { const emptySpace = cut.element .getElementsByClassName('dv-void-container') - .item(0); + .item(0) as HTMLElement; if (!emptySpace!) { fail('element not found'); } - jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation( () => 100 ); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 0d00d1a31..8c0dab657 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -133,11 +133,11 @@ describe('dockviewComponent', () => { }, className: 'test-a test-b', }); - expect(dockview.element.className).toBe('test-a test-b'); + expect(dockview.element.className).toBe('test-a test-b dockview-theme-abyss'); dockview.updateOptions({ className: 'test-b test-c' }); - expect(dockview.element.className).toBe('test-b test-c'); + expect(dockview.element.className).toBe('dockview-theme-abyss test-b test-c'); }); describe('memory leakage', () => { @@ -3339,10 +3339,10 @@ describe('dockviewComponent', () => { position: { direction: 'right' }, }); - Object.defineProperty(dockview.element, 'clientWidth', { + Object.defineProperty(dockview.element, 'offsetWidth', { get: () => 100, }); - Object.defineProperty(dockview.element, 'clientHeight', { + Object.defineProperty(dockview.element, 'offsetHeight', { get: () => 100, }); @@ -6652,36 +6652,4 @@ describe('dockviewComponent', () => { expect(api.panels.length).toBe(3); expect(api.groups.length).toBe(3); }); - - describe('updateOptions', () => { - test('gap', () => { - const container = document.createElement('div'); - - const dockview = new DockviewComponent(container, { - createComponent(options) { - switch (options.name) { - case 'default': - return new PanelContentPartTest( - options.id, - options.name - ); - default: - throw new Error(`unsupported`); - } - }, - gap: 6, - }); - - expect(dockview.gap).toBe(6); - - dockview.updateOptions({ gap: 10 }); - expect(dockview.gap).toBe(10); - - dockview.updateOptions({}); - expect(dockview.gap).toBe(10); - - dockview.updateOptions({ gap: 15 }); - expect(dockview.gap).toBe(15); - }); - }); }); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts index 55aed39ec..19b811c4f 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts @@ -684,12 +684,12 @@ describe('dockviewGroupPanelModel', () => { const element = container .getElementsByClassName('dv-content-container') - .item(0)!; + .item(0)! as HTMLElement; - jest.spyOn(element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100); + jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100); fireEvent.dragEnter(element); fireEvent.dragOver(element); @@ -744,12 +744,12 @@ describe('dockviewGroupPanelModel', () => { const element = container .getElementsByClassName('dv-content-container') - .item(0)!; + .item(0)! as HTMLElement; - jest.spyOn(element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100); + jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100); function run(value: number) { fireEvent.dragEnter(element); @@ -792,7 +792,7 @@ describe('dockviewGroupPanelModel', () => { fireEvent.dragEnd(element); }); - test('that should not show drop target if dropping on self', () => { + test('that should show drop target if dropping on self', () => { const accessor = fromPartial({ id: 'testcomponentid', options: {}, @@ -806,15 +806,9 @@ describe('dockviewGroupPanelModel', () => { ), }); - const groupviewMock = jest.fn, []>( - () => { - return { - canDisplayOverlay: jest.fn(), - }; - } - ); - - const groupView = new groupviewMock() as DockviewGroupPanelModel; + const groupView = fromPartial({ + canDisplayOverlay: jest.fn(), + }); const groupPanelMock = jest.fn, []>(() => { return { @@ -842,12 +836,12 @@ describe('dockviewGroupPanelModel', () => { const element = container .getElementsByClassName('dv-content-container') - .item(0)!; + .item(0)! as HTMLElement; - jest.spyOn(element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100); + jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100); LocalSelectionTransfer.getInstance().setData( [new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')], @@ -861,10 +855,10 @@ describe('dockviewGroupPanelModel', () => { expect( element.getElementsByClassName('dv-drop-target-dropzone').length - ).toBe(0); + ).toBe(1); }); - test('that should not allow drop when dropping on self for same component id', () => { + test('that should allow drop when dropping on self for same component id', () => { const accessor = fromPartial({ id: 'testcomponentid', options: {}, @@ -915,12 +909,12 @@ describe('dockviewGroupPanelModel', () => { const element = container .getElementsByClassName('dv-content-container') - .item(0)!; + .item(0) as HTMLElement; - jest.spyOn(element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100); + jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100); LocalSelectionTransfer.getInstance().setData( [new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')], @@ -934,7 +928,7 @@ describe('dockviewGroupPanelModel', () => { expect( element.getElementsByClassName('dv-drop-target-dropzone').length - ).toBe(0); + ).toBe(1); }); test('that should not allow drop when not dropping for different component id', () => { @@ -988,12 +982,12 @@ describe('dockviewGroupPanelModel', () => { const element = container .getElementsByClassName('dv-content-container') - .item(0)!; + .item(0) as HTMLElement; - jest.spyOn(element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100); + jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100); LocalSelectionTransfer.getInstance().setData( [new PanelTransfer('anothercomponentid', 'groupviewid', 'panel1')], diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 6884c93aa..0b0850ed0 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -629,10 +629,20 @@ export class DockviewApi implements CommonApi { return this.component.totalPanels; } + /** + * @deprecated dockview: dockviewComponent.gap has been deprecated. Use `theme` instead. This will be removed in a future version. + */ get gap(): number { return this.component.gap; } + /** + * @deprecated dockview: dockviewComponent.setGap has been deprecated. Use `theme` instead. This will be removed in a future version. + */ + setGap(gap: number | undefined): void { + this.component.updateOptions({ gap: gap }); + } + /** * Invoked when the active group changes. May be undefined if no group is active. */ @@ -914,10 +924,6 @@ export class DockviewApi implements CommonApi { return this.component.addPopoutGroup(item, options); } - setGap(gap: number | undefined): void { - this.component.updateOptions({ gap }); - } - updateOptions(options: Partial) { this.component.updateOptions(options); } diff --git a/packages/dockview-core/src/dnd/abstractDragHandler.ts b/packages/dockview-core/src/dnd/abstractDragHandler.ts index 84345c160..7ba701034 100644 --- a/packages/dockview-core/src/dnd/abstractDragHandler.ts +++ b/packages/dockview-core/src/dnd/abstractDragHandler.ts @@ -67,7 +67,7 @@ export abstract class DragHandler extends CompositeDisposable { * For example: in react-dnd if dataTransfer.types is not set then the dragStart event will be cancelled * through .preventDefault(). Since this is applied globally to all drag events this would break dockviews * dnd logic. You can see the code at - * https://github.com/react-dnd/react-dnd/blob/main/packages/backend-html5/src/HTML5BackendImpl.ts#L542 + P * https://github.com/react-dnd/react-dnd/blob/main/packages/backend-html5/src/HTML5BackendImpl.ts#L542 */ event.dataTransfer.setData('text/plain', ''); } @@ -75,7 +75,9 @@ export abstract class DragHandler extends CompositeDisposable { }), addDisposableListener(this.el, 'dragend', () => { this.pointerEventsDisposable.dispose(); - this.dataDisposable.dispose(); + setTimeout(() => { + this.dataDisposable.dispose(); // allow the data to be read by other handlers before disposing + }, 0); }) ); } diff --git a/packages/dockview-core/src/dnd/dropTargetAnchorContainer.scss b/packages/dockview-core/src/dnd/dropTargetAnchorContainer.scss new file mode 100644 index 000000000..0fd3dc5a5 --- /dev/null +++ b/packages/dockview-core/src/dnd/dropTargetAnchorContainer.scss @@ -0,0 +1,23 @@ +.dv-drop-target-container { + position: absolute; + z-index: 9999; + top: 0px; + left: 0px; + height: 100%; + width: 100%; + pointer-events: none; + overflow: hidden; + --dv-transition-duration: 300ms; + + .dv-drop-target-anchor { + position: relative; + border: var(--dv-drag-over-border); + transition: opacity var(--dv-transition-duration) ease-in, + top var(--dv-transition-duration) ease-out, + left var(--dv-transition-duration) ease-out, + width var(--dv-transition-duration) ease-out, + height var(--dv-transition-duration) ease-out; + background-color: var(--dv-drag-over-background-color); + opacity: 1; + } +} diff --git a/packages/dockview-core/src/dnd/dropTargetAnchorContainer.ts b/packages/dockview-core/src/dnd/dropTargetAnchorContainer.ts new file mode 100644 index 000000000..e43989c9e --- /dev/null +++ b/packages/dockview-core/src/dnd/dropTargetAnchorContainer.ts @@ -0,0 +1,102 @@ +import { CompositeDisposable, Disposable } from '../lifecycle'; +import { DropTargetTargetModel } from './droptarget'; + +export class DropTargetAnchorContainer extends CompositeDisposable { + private _model: + | { root: HTMLElement; overlay: HTMLElement; changed: boolean } + | undefined; + + private _outline: HTMLElement | undefined; + + private _disabled = false; + + get disabled(): boolean { + return this._disabled; + } + + set disabled(value: boolean) { + if (this.disabled === value) { + return; + } + + this._disabled = value; + + if (value) { + this.model?.clear(); + } + } + + get model(): DropTargetTargetModel | undefined { + if (this.disabled) { + return undefined; + } + + return { + clear: () => { + if (this._model) { + this._model.root.parentElement?.removeChild( + this._model.root + ); + } + this._model = undefined; + }, + exists: () => { + return !!this._model; + }, + getElements: (event?: DragEvent, outline?: HTMLElement) => { + const changed = this._outline !== outline; + this._outline = outline; + + if (this._model) { + this._model.changed = changed; + return this._model; + } + + const container = this.createContainer(); + const anchor = this.createAnchor(); + + this._model = { root: container, overlay: anchor, changed }; + + container.appendChild(anchor); + this.element.appendChild(container); + + if (event?.target instanceof HTMLElement) { + const targetBox = event.target.getBoundingClientRect(); + const box = this.element.getBoundingClientRect(); + + anchor.style.left = `${targetBox.left - box.left}px`; + anchor.style.top = `${targetBox.top - box.top}px`; + } + + return this._model; + }, + }; + } + + constructor(readonly element: HTMLElement, options: { disabled: boolean }) { + super(); + + this._disabled = options.disabled; + + this.addDisposables( + Disposable.from(() => { + this.model?.clear(); + }) + ); + } + + private createContainer(): HTMLElement { + const el = document.createElement('div'); + el.className = 'dv-drop-target-container'; + + return el; + } + + private createAnchor(): HTMLElement { + const el = document.createElement('div'); + el.className = 'dv-drop-target-anchor'; + el.style.visibility = 'hidden'; + + return el; + } +} diff --git a/packages/dockview-core/src/dnd/droptarget.scss b/packages/dockview-core/src/dnd/droptarget.scss index f23f318f7..7f2c8cb8b 100644 --- a/packages/dockview-core/src/dnd/droptarget.scss +++ b/packages/dockview-core/src/dnd/droptarget.scss @@ -1,5 +1,6 @@ .dv-drop-target { position: relative; + --dv-transition-duration: 70ms; > .dv-drop-target-dropzone { position: absolute; @@ -15,10 +16,13 @@ box-sizing: border-box; height: 100%; width: 100%; + border: var(--dv-drag-over-border); background-color: var(--dv-drag-over-background-color); - transition: top 70ms ease-out, left 70ms ease-out, - width 70ms ease-out, height 70ms ease-out, - opacity 0.15s ease-out; + transition: top var(--dv-transition-duration) ease-out, + left var(--dv-transition-duration) ease-out, + width var(--dv-transition-duration) ease-out, + height var(--dv-transition-duration) ease-out, + opacity var(--dv-transition-duration) ease-out; will-change: transform; pointer-events: none; diff --git a/packages/dockview-core/src/dnd/droptarget.ts b/packages/dockview-core/src/dnd/droptarget.ts index 702fed867..c611f890b 100644 --- a/packages/dockview-core/src/dnd/droptarget.ts +++ b/packages/dockview-core/src/dnd/droptarget.ts @@ -93,10 +93,26 @@ const DEFAULT_SIZE: MeasuredValue = { const SMALL_WIDTH_BOUNDARY = 100; const SMALL_HEIGHT_BOUNDARY = 100; +export interface DropTargetTargetModel { + getElements( + event?: DragEvent, + outline?: HTMLElement + ): { + root: HTMLElement; + overlay: HTMLElement; + changed: boolean; + }; + exists(): boolean; + clear(): void; +} + export interface DroptargetOptions { canDisplayOverlay: CanDisplayOverlay; acceptedTargetZones: Position[]; overlayModel?: DroptargetOverlayModel; + getOverrideTarget?: () => DropTargetTargetModel | undefined; + className?: string; + getOverlayOutline?: () => HTMLElement | null; } export class Droptarget extends CompositeDisposable { @@ -116,6 +132,18 @@ export class Droptarget extends CompositeDisposable { private static USED_EVENT_ID = '__dockview_droptarget_event_is_used__'; + private static ACTUAL_TARGET: Droptarget | undefined; + + private _disabled: boolean; + + get disabled(): boolean { + return this._disabled; + } + + set disabled(value: boolean) { + this._disabled = value; + } + get state(): Position | undefined { return this._state; } @@ -126,21 +154,35 @@ export class Droptarget extends CompositeDisposable { ) { super(); + this._disabled = false; + // use a set to take advantage of #.has this._acceptedTargetZonesSet = new Set( this.options.acceptedTargetZones ); this.dnd = new DragAndDropObserver(this.element, { - onDragEnter: () => undefined, + onDragEnter: () => { + this.options.getOverrideTarget?.()?.getElements(); + }, onDragOver: (e) => { + Droptarget.ACTUAL_TARGET = this; + + const overrideTraget = this.options.getOverrideTarget?.(); + if (this._acceptedTargetZonesSet.size === 0) { + if (overrideTraget) { + return; + } this.removeDropTarget(); return; } - const width = this.element.clientWidth; - const height = this.element.clientHeight; + const target = + this.options.getOverlayOutline?.() ?? this.element; + + const width = target.offsetWidth; + const height = target.offsetHeight; if (width === 0 || height === 0) { return; // avoid div!0 @@ -149,8 +191,8 @@ export class Droptarget extends CompositeDisposable { const rect = ( e.currentTarget as HTMLElement ).getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; + const x = (e.clientX ?? 0) - rect.left; + const y = (e.clientY ?? 0) - rect.top; const quadrant = this.calculateQuadrant( this._acceptedTargetZonesSet, @@ -172,6 +214,9 @@ export class Droptarget extends CompositeDisposable { } if (!this.options.canDisplayOverlay(e, quadrant)) { + if (overrideTraget) { + return; + } this.removeDropTarget(); return; } @@ -194,7 +239,9 @@ export class Droptarget extends CompositeDisposable { this.markAsUsed(e); - if (!this.targetElement) { + if (overrideTraget) { + // + } else if (!this.targetElement) { this.targetElement = document.createElement('div'); this.targetElement.className = 'dv-drop-target-dropzone'; this.overlayElement = document.createElement('div'); @@ -202,8 +249,16 @@ export class Droptarget extends CompositeDisposable { this._state = 'center'; this.targetElement.appendChild(this.overlayElement); - this.element.classList.add('dv-drop-target'); - this.element.append(this.targetElement); + target.classList.add('dv-drop-target'); + target.append(this.targetElement); + + // this.overlayElement.style.opacity = '0'; + + // requestAnimationFrame(() => { + // if (this.overlayElement) { + // this.overlayElement.style.opacity = ''; + // } + // }); } this.toggleClasses(quadrant, width, height); @@ -211,10 +266,32 @@ export class Droptarget extends CompositeDisposable { this._state = quadrant; }, onDragLeave: () => { + const target = this.options.getOverrideTarget?.(); + + if (target) { + return; + } + this.removeDropTarget(); }, - onDragEnd: () => { + onDragEnd: (e) => { + const target = this.options.getOverrideTarget?.(); + + if (target && Droptarget.ACTUAL_TARGET === this) { + if (this._state) { + // only stop the propagation of the event if we are dealing with it + // which is only when the target has state + e.stopPropagation(); + this._onDrop.fire({ + position: this._state, + nativeEvent: e, + }); + } + } + this.removeDropTarget(); + + target?.clear(); }, onDrop: (e) => { e.preventDefault(); @@ -223,6 +300,8 @@ export class Droptarget extends CompositeDisposable { this.removeDropTarget(); + this.options.getOverrideTarget?.()?.clear(); + if (state) { // only stop the propagation of the event if we are dealing with it // which is only when the target has state @@ -268,7 +347,9 @@ export class Droptarget extends CompositeDisposable { width: number, height: number ): void { - if (!this.overlayElement) { + const target = this.options.getOverrideTarget?.(); + + if (!target && !this.overlayElement) { return; } @@ -300,6 +381,103 @@ export class Droptarget extends CompositeDisposable { } } + if (target) { + const outlineEl = + this.options.getOverlayOutline?.() ?? this.element; + const elBox = outlineEl.getBoundingClientRect(); + + const ta = target.getElements(undefined, outlineEl); + const el = ta.root; + const overlay = ta.overlay; + + const bigbox = el.getBoundingClientRect(); + + const rootTop = elBox.top - bigbox.top; + const rootLeft = elBox.left - bigbox.left; + + const box = { + top: rootTop, + left: rootLeft, + width: width, + height: height, + }; + + if (rightClass) { + box.left = rootLeft + width * (1 - size); + box.width = width * size; + } else if (leftClass) { + box.width = width * size; + } else if (topClass) { + box.height = height * size; + } else if (bottomClass) { + box.top = rootTop + height * (1 - size); + box.height = height * size; + } + + if (isSmallX && isLeft) { + box.width = 4; + } + if (isSmallX && isRight) { + box.left = rootLeft + width - 4; + box.width = 4; + } + + const topPx = `${Math.round(box.top)}px`; + const leftPx = `${Math.round(box.left)}px`; + const widthPx = `${Math.round(box.width)}px`; + const heightPx = `${Math.round(box.height)}px`; + + if ( + overlay.style.top === topPx && + overlay.style.left === leftPx && + overlay.style.width === widthPx && + overlay.style.height === heightPx + ) { + return; + } + + overlay.style.top = topPx; + overlay.style.left = leftPx; + overlay.style.width = widthPx; + overlay.style.height = heightPx; + overlay.style.visibility = 'visible'; + + overlay.className = `dv-drop-target-anchor${ + this.options.className ? ` ${this.options.className}` : '' + }`; + + toggleClass(overlay, 'dv-drop-target-left', isLeft); + toggleClass(overlay, 'dv-drop-target-right', isRight); + toggleClass(overlay, 'dv-drop-target-top', isTop); + toggleClass(overlay, 'dv-drop-target-bottom', isBottom); + toggleClass( + overlay, + 'dv-drop-target-center', + quadrant === 'center' + ); + + if (ta.changed) { + toggleClass( + overlay, + 'dv-drop-target-anchor-container-changed', + true + ); + setTimeout(() => { + toggleClass( + overlay, + 'dv-drop-target-anchor-container-changed', + false + ); + }, 10); + } + + return; + } + + if (!this.overlayElement) { + return; + } + const box = { top: '0px', left: '0px', width: '100%', height: '100%' }; /** @@ -396,10 +574,12 @@ export class Droptarget extends CompositeDisposable { private removeDropTarget(): void { if (this.targetElement) { this._state = undefined; - this.element.removeChild(this.targetElement); + this.targetElement.parentElement?.classList.remove( + 'dv-drop-target' + ); + this.targetElement.remove(); this.targetElement = undefined; this.overlayElement = undefined; - this.element.classList.remove('dv-drop-target'); } } } diff --git a/packages/dockview-core/src/dnd/ghost.ts b/packages/dockview-core/src/dnd/ghost.ts index 2ff9c569f..df976c7cf 100644 --- a/packages/dockview-core/src/dnd/ghost.ts +++ b/packages/dockview-core/src/dnd/ghost.ts @@ -2,13 +2,14 @@ import { addClasses, removeClasses } from '../dom'; export function addGhostImage( dataTransfer: DataTransfer, - ghostElement: HTMLElement + ghostElement: HTMLElement, + options?: { x?: number; y?: number } ): void { // class dockview provides to force ghost image to be drawn on a different layer and prevent weird rendering issues addClasses(ghostElement, 'dv-dragged'); document.body.appendChild(ghostElement); - dataTransfer.setDragImage(ghostElement, 0, 0); + dataTransfer.setDragImage(ghostElement, options?.x ?? 0, options?.y ?? 0); setTimeout(() => { removeClasses(ghostElement, 'dv-dragged'); diff --git a/packages/dockview-core/src/dnd/groupDragHandler.ts b/packages/dockview-core/src/dnd/groupDragHandler.ts index bdda2be3b..2e3c9d281 100644 --- a/packages/dockview-core/src/dnd/groupDragHandler.ts +++ b/packages/dockview-core/src/dnd/groupDragHandler.ts @@ -72,9 +72,11 @@ export class GroupDragHandler extends DragHandler { ghostElement.style.lineHeight = '20px'; ghostElement.style.borderRadius = '12px'; ghostElement.style.position = 'absolute'; + ghostElement.style.pointerEvents = 'none'; + ghostElement.style.top = '-9999px'; ghostElement.textContent = `Multiple Panels (${this.group.size})`; - addGhostImage(dataTransfer, ghostElement); + addGhostImage(dataTransfer, ghostElement, { y: -10, x: 30 }); } return { diff --git a/packages/dockview-core/src/dockview/components/panel/content.ts b/packages/dockview-core/src/dockview/components/panel/content.ts index 4f66b03d3..08703179d 100644 --- a/packages/dockview-core/src/dockview/components/panel/content.ts +++ b/packages/dockview-core/src/dockview/components/panel/content.ts @@ -55,7 +55,15 @@ export class ContentContainer this.addDisposables(this._onDidFocus, this._onDidBlur); + const target = group.dropTargetContainer; + this.dropTarget = new Droptarget(this.element, { + getOverlayOutline: () => { + return accessor.options.theme?.includeHeaderWhenHoverOverContent + ? this.element.parentElement + : null; + }, + className: 'dv-drop-target-content', acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'], canDisplayOverlay: (event, position) => { if ( @@ -76,26 +84,12 @@ export class ContentContainer } if (data && data.viewId === this.accessor.id) { - if (data.groupId === this.group.id) { - if (position === 'center') { - // don't allow to drop on self for center position - return false; - } - if (data.panelId === null) { - // don't allow group move to drop anywhere on self - return false; - } - } - - const groupHasOnePanelAndIsActiveDragElement = - this.group.panels.length === 1 && - data.groupId === this.group.id; - - return !groupHasOnePanelAndIsActiveDragElement; + return true; } return this.group.canDisplayOverlay(event, position, 'content'); }, + getOverrideTarget: target ? () => target.model : undefined, }); this.addDisposables(this.dropTarget); diff --git a/packages/dockview-core/src/dockview/components/tab/defaultTab.scss b/packages/dockview-core/src/dockview/components/tab/defaultTab.scss index 0fdf53d78..3d2865583 100644 --- a/packages/dockview-core/src/dockview/components/tab/defaultTab.scss +++ b/packages/dockview-core/src/dockview/components/tab/defaultTab.scss @@ -58,15 +58,13 @@ position: relative; height: 100%; display: flex; - min-width: 80px; align-items: center; - padding: 0px 8px; white-space: nowrap; text-overflow: ellipsis; .dv-default-tab-content { - padding: 0px 8px; flex-grow: 1; + margin-right: 4px; } .dv-default-tab-action { diff --git a/packages/dockview-core/src/dockview/components/tab/tab.ts b/packages/dockview-core/src/dockview/components/tab/tab.ts index 1eb1174d8..9b1975d4a 100644 --- a/packages/dockview-core/src/dockview/components/tab/tab.ts +++ b/packages/dockview-core/src/dockview/components/tab/tab.ts @@ -16,6 +16,7 @@ import { } from '../../../dnd/droptarget'; import { DragHandler } from '../../../dnd/abstractDragHandler'; import { IDockviewPanel } from '../../dockviewPanel'; +import { addGhostImage } from '../../../dnd/ghost'; class TabDragHandler extends DragHandler { private readonly panelTransfer = @@ -86,7 +87,8 @@ export class Tab extends CompositeDisposable { ); this.dropTarget = new Droptarget(this._element, { - acceptedTargetZones: ['center'], + acceptedTargetZones: ['left', 'right'], + overlayModel: { activationSize: { value: 50, type: 'percentage' } }, canDisplayOverlay: (event, position) => { if (this.group.locked) { return false; @@ -95,15 +97,7 @@ export class Tab extends CompositeDisposable { const data = getPanelData(); if (data && this.accessor.id === data.viewId) { - if ( - data.panelId === null && - data.groupId === this.group.id - ) { - // don't allow group move to drop on self - return false; - } - - return this.panel.id !== data.panelId; + return true; } return this.group.model.canDisplayOverlay( @@ -112,6 +106,7 @@ export class Tab extends CompositeDisposable { 'tab' ); }, + getOverrideTarget: () => group.model.dropTargetContainer?.model, }); this.onWillShowOverlay = this.dropTarget.onWillShowOverlay; @@ -121,6 +116,23 @@ export class Tab extends CompositeDisposable { this._onDropped, this._onDragStart, dragHandler.onDragStart((event) => { + if (event.dataTransfer) { + const style = getComputedStyle(this.element); + const newNode = this.element.cloneNode(true) as HTMLElement; + Array.from(style).forEach((key) => + newNode.style.setProperty( + key, + style.getPropertyValue(key), + style.getPropertyPriority(key) + ) + ); + newNode.style.position = 'absolute'; + + addGhostImage(event.dataTransfer, newNode, { + y: -10, + x: 30, + }); + } this._onDragStart.fire(event); }), dragHandler, diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss index fef520e03..86777ec9e 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss @@ -7,17 +7,17 @@ font-size: var(--dv-tabs-and-actions-container-font-size); &.dv-single-tab.dv-full-width-single-tab { - .dv-tabs-container { - flex-grow: 1; - - .dv-tab { + .dv-tabs-container { flex-grow: 1; - } - } - .dv-void-container { - flex-grow: 0; - } + .dv-tab { + flex-grow: 1; + } + } + + .dv-void-container { + flex-grow: 0; + } } .dv-void-container { @@ -50,10 +50,20 @@ .dv-tab { -webkit-user-drag: element; outline: none; - min-width: 75px; + padding: 0.25rem 0.5rem; cursor: pointer; position: relative; box-sizing: border-box; + font-size: var(-dv-tab-font-size); + margin: var(--dv-tab-margin); + + &:first-child { + margin-right: 0; + } + + &:not(:nth-last-child(1)) { + margin-left: 0; + } &:not(:first-child)::before { content: ' '; diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index d3bd0568b..c6af57973 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -10,7 +10,10 @@ import { VoidContainer } from './voidContainer'; import { toggleClass } from '../../../dom'; import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel'; import { DockviewComponent } from '../../dockviewComponent'; -import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel'; +import { + DockviewGroupPanelModel, + WillShowOverlayLocationEvent, +} from '../../dockviewGroupPanelModel'; import { getPanelData } from '../../../dnd/dataTransfer'; export interface TabDropIndexEvent { diff --git a/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts index 6e9ea0c47..29e31b9b6 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts @@ -1,4 +1,3 @@ -import { last } from '../../../array'; import { getPanelData } from '../../../dnd/dataTransfer'; import { Droptarget, @@ -10,6 +9,7 @@ import { DockviewComponent } from '../../dockviewComponent'; import { addDisposableListener, Emitter, Event } from '../../../events'; import { CompositeDisposable } from '../../../lifecycle'; import { DockviewGroupPanel } from '../../dockviewGroupPanel'; +import { DockviewGroupPanelModel } from '../../dockviewGroupPanelModel'; export class VoidContainer extends CompositeDisposable { private readonly _element: HTMLElement; @@ -54,16 +54,7 @@ export class VoidContainer extends CompositeDisposable { const data = getPanelData(); if (data && this.accessor.id === data.viewId) { - if ( - data.panelId === null && - data.groupId === this.group.id - ) { - // don't allow group move to drop on self - return false; - } - - // don't show the overlay if the tab being dragged is the last panel of this group - return last(this.group.panels)?.id !== data.panelId; + return true; } return group.model.canDisplayOverlay( @@ -72,6 +63,7 @@ export class VoidContainer extends CompositeDisposable { 'header_space' ); }, + getOverrideTarget: () => group.model.dropTargetContainer?.model, }); this.onWillShowOverlay = this.dropTraget.onWillShowOverlay; diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 0dde93454..86465c015 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -54,6 +54,7 @@ import { Parameters } from '../panel/types'; import { Overlay } from '../overlay/overlay'; import { addTestId, + Classnames, getDockviewTheme, toggleClass, watchElementResize, @@ -74,6 +75,8 @@ import { } from '../overlay/overlayRenderContainer'; import { PopoutWindow } from '../popoutWindow'; import { StrictEventsSequencing } from './strictEventsSequencing'; +import { DropTargetAnchorContainer } from '../dnd/dropTargetAnchorContainer'; +import { DockviewTheme, themeAbyss } from './theme'; const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = { activationSize: { type: 'pixels', value: 10 }, @@ -191,6 +194,9 @@ export interface IDockviewComponent extends IBaseGrid { readonly totalPanels: number; readonly panels: IDockviewPanel[]; readonly orientation: Orientation; + /** + * @deprecated use `theme` instead. This will be removed in a future version + */ readonly gap: number; readonly onDidDrop: Event; readonly onWillDrop: Event; @@ -253,9 +259,11 @@ export class DockviewComponent private readonly _deserializer = new DefaultDockviewDeserialzier(this); private readonly _api: DockviewApi; private _options: Exclude; - private watermark: IWatermarkRenderer | null = null; + private _watermark: IWatermarkRenderer | null = null; + private readonly _themeClassnames: Classnames; readonly overlayRenderContainer: OverlayRenderContainer; + readonly rootDropTargetContainer: DropTargetAnchorContainer; private readonly _onWillDragPanel = new Emitter(); readonly onWillDragPanel: Event = this._onWillDragPanel.event; @@ -361,6 +369,9 @@ export class DockviewComponent } get gap(): number { + console.warn( + 'dockview: dockviewComponent.gap has been deprecated. Use `theme` instead. This will be removed in a future version.' + ); return this.gridview.margin; } @@ -377,10 +388,18 @@ export class DockviewComponent : undefined, disableAutoResizing: options.disableAutoResizing, locked: options.locked, - margin: options.gap, + margin: options.theme?.gap ?? 0, className: options.className, }); + this.updateDropTargetModel(options); + + this._themeClassnames = new Classnames(this.element); + + this.rootDropTargetContainer = new DropTargetAnchorContainer( + this.element, + { disabled: true } + ); this.overlayRenderContainer = new OverlayRenderContainer( this.gridview.element, this @@ -394,6 +413,7 @@ export class DockviewComponent } this.addDisposables( + this.rootDropTargetContainer, this.overlayRenderContainer, this._onWillDragPanel, this._onWillDragGroup, @@ -464,8 +484,10 @@ export class DockviewComponent ); this._options = options; + this.updateTheme(); this._rootDropTarget = new Droptarget(this.element, { + className: 'dv-drop-target-edge', canDisplayOverlay: (event, position) => { const data = getPanelData(); @@ -506,6 +528,7 @@ export class DockviewComponent acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'], overlayModel: this.options.rootOverlayModel ?? DEFAULT_ROOT_OVERLAY_MODEL, + getOverrideTarget: () => this.rootDropTargetContainer?.model, }); this.addDisposables( @@ -756,6 +779,15 @@ export class DockviewComponent popoutContainer.appendChild(group.element); + const anchor = document.createElement('div'); + const dropTargetContainer = new DropTargetAnchorContainer( + anchor, + { disabled: this.rootDropTargetContainer.disabled } + ); + popoutContainer.appendChild(anchor); + + group.model.dropTargetContainer = dropTargetContainer; + group.model.location = { type: 'popout', getWindow: () => _window.window!, @@ -844,6 +876,8 @@ export class DockviewComponent } else if (this.getPanel(group.id)) { group.model.renderContainer = this.overlayRenderContainer; + group.model.dropTargetContainer = + this.rootDropTargetContainer; returnedGroup = group; const alreadyRemoved = !this._popoutGroups.find( @@ -1134,6 +1168,13 @@ export class DockviewComponent override updateOptions(options: Partial): void { super.updateOptions(options); + if ('gap' in options) { + console.warn( + 'dockview: dockviewComponent.setGap has been deprecated. Use `theme` instead. This will be removed in a future version.' + ); + this.gridview.margin = options.gap ?? 0; + } + if ('floatingGroupBounds' in options) { for (const group of this._floatingGroups) { switch (options.floatingGroupBounds) { @@ -1158,18 +1199,14 @@ export class DockviewComponent } } - if ('rootOverlayModel' in options) { - this._rootDropTarget.setOverlayModel( - options.rootOverlayModel ?? DEFAULT_ROOT_OVERLAY_MODEL - ); - } - - if ('gap' in options) { - this.gridview.margin = options.gap ?? 0; - } + this.updateDropTargetModel(options); this._options = { ...this.options, ...options }; + if ('theme' in options) { + this.updateTheme(); + } + this.layout(this.gridview.width, this.gridview.height, true); } @@ -1745,24 +1782,24 @@ export class DockviewComponent (x) => x.api.location.type === 'grid' && x.api.isVisible ).length === 0 ) { - if (!this.watermark) { - this.watermark = this.createWatermarkComponent(); + if (!this._watermark) { + this._watermark = this.createWatermarkComponent(); - this.watermark.init({ + this._watermark.init({ containerApi: new DockviewApi(this), }); const watermarkContainer = document.createElement('div'); watermarkContainer.className = 'dv-watermark-container'; addTestId(watermarkContainer, 'watermark-component'); - watermarkContainer.appendChild(this.watermark.element); + watermarkContainer.appendChild(this._watermark.element); this.gridview.element.appendChild(watermarkContainer); } - } else if (this.watermark) { - this.watermark.element.parentElement!.remove(); - this.watermark.dispose?.(); - this.watermark = null; + } else if (this._watermark) { + this._watermark.element.parentElement!.remove(); + this._watermark.dispose?.(); + this._watermark = null; } } @@ -2404,9 +2441,11 @@ export class DockviewComponent if (this._moving) { return; } + if (event.panel !== this.activePanel) { return; } + if (this._onDidActivePanelChange.value !== event.panel) { this._onDidActivePanelChange.fire(event.panel); } @@ -2489,4 +2528,44 @@ export class DockviewComponent ? rootOrientation : orthogonal(rootOrientation); } + + private updateDropTargetModel(options: Partial) { + if ('dndEdges' in options) { + this._rootDropTarget.disabled = + typeof options.dndEdges === 'boolean' && + options.dndEdges === false; + + if ( + typeof options.dndEdges === 'object' && + options.dndEdges !== null + ) { + this._rootDropTarget.setOverlayModel(options.dndEdges); + } else { + this._rootDropTarget.setOverlayModel( + DEFAULT_ROOT_OVERLAY_MODEL + ); + } + } + + if ('rootOverlayModel' in options) { + this.updateDropTargetModel({ dndEdges: options.dndEdges }); + } + } + + private updateTheme(): void { + const theme = this._options.theme ?? themeAbyss; + this._themeClassnames.setClassNames(theme.className); + + this.gridview.margin = theme.gap ?? 0; + + switch (theme.dndOverlayMounting) { + case 'absolute': + this.rootDropTargetContainer.disabled = false; + break; + case 'relative': + default: + this.rootDropTargetContainer.disabled = true; + break; + } + } } diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index a34d5ef10..c56babf91 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -39,6 +39,7 @@ import { import { OverlayRenderContainer } from '../overlay/overlayRenderContainer'; import { TitleEvent } from '../api/dockviewPanelApi'; import { Contraints } from '../gridview/gridviewPanel'; +import { DropTargetAnchorContainer } from '../dnd/dropTargetAnchorContainer'; interface GroupMoveEvent { groupId: string; @@ -265,6 +266,8 @@ export class DockviewGroupPanelModel private mostRecentlyUsed: IDockviewPanel[] = []; private _overwriteRenderContainer: OverlayRenderContainer | null = null; + private _overwriteDropTargetContainer: DropTargetAnchorContainer | null = + null; private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = @@ -535,6 +538,17 @@ export class DockviewGroupPanelModel ); } + set dropTargetContainer(value: DropTargetAnchorContainer | null) { + this._overwriteDropTargetContainer = value; + } + + get dropTargetContainer(): DropTargetAnchorContainer | null { + return ( + this._overwriteDropTargetContainer ?? + this.accessor.rootDropTargetContainer + ); + } + initialize(): void { if (this.options.panels) { this.options.panels.forEach((panel) => { @@ -1049,6 +1063,29 @@ export class DockviewGroupPanelModel const data = getPanelData(); if (data && data.viewId === this.accessor.id) { + if (type === 'content') { + if (data.groupId === this.id) { + // don't allow to drop on self for center position + + if (position === 'center') { + return; + } + + if (data.panelId === null) { + // don't allow group move to drop anywhere on self + return; + } + } + } + + if (type === 'header') { + if (data.groupId === this.id) { + if (data.panelId === null) { + return; + } + } + } + if (data.panelId === null) { // this is a group move dnd event const { groupId } = data; diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 3f7b94367..4b096c941 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -17,6 +17,7 @@ import { IGroupHeaderProps } from './framework'; import { FloatingGroupOptions } from './dockviewComponent'; import { Contraints } from '../gridview/gridviewPanel'; import { AcceptableEvent, IAcceptableEvent } from '../events'; +import { DockviewTheme } from './theme'; export interface IHeaderActionsRenderer extends IDisposable { readonly element: HTMLElement; @@ -51,19 +52,26 @@ export interface DockviewOptions { }; popoutUrl?: string; defaultRenderer?: DockviewPanelRenderer; - debug?: boolean; - rootOverlayModel?: DroptargetOverlayModel; - locked?: boolean; - disableDnd?: boolean; - className?: string; /** - * Pixel gap between groups + * @deprecated dockview: dockviewComponent.gap has been deprecated. Use `theme` instead. This will be removed in a future version. */ gap?: number; + debug?: boolean; + // #start dnd + dndEdges?: false | DroptargetOverlayModel; + /** + * @deprecated use `dndEdges` instead. To be removed in a future version. + * */ + rootOverlayModel?: DroptargetOverlayModel; + disableDnd?: boolean; + // #end dnd + locked?: boolean; + className?: string; /** * Define the behaviour of the dock when there are no panels to display. Defaults to `watermark`. */ noPanelsOverlay?: 'emptyGroup' | 'watermark'; + theme?: DockviewTheme; } export interface DockviewDndOverlayEvent extends IAcceptableEvent { @@ -106,9 +114,11 @@ export const PROPERTY_KEYS_DOCKVIEW: (keyof DockviewOptions)[] = (() => { rootOverlayModel: undefined, locked: undefined, disableDnd: undefined, - gap: undefined, className: undefined, noPanelsOverlay: undefined, + dndEdges: undefined, + theme: undefined, + gap: undefined, }; return Object.keys(properties) as (keyof DockviewOptions)[]; diff --git a/packages/dockview-core/src/dockview/theme.ts b/packages/dockview-core/src/dockview/theme.ts new file mode 100644 index 000000000..4a921e2db --- /dev/null +++ b/packages/dockview-core/src/dockview/theme.ts @@ -0,0 +1,54 @@ +export interface DockviewTheme { + name: string; + className: string; + gap?: number; + dndOverlayMounting?: 'absolute' | 'relative'; + includeHeaderWhenHoverOverContent?: boolean; +} + +export const themeDark: DockviewTheme = { + name: 'dark', + className: 'dockview-theme-dark', +}; + +export const themeLight: DockviewTheme = { + name: 'light', + className: 'dockview-theme-light', +}; + +export const themeVisualStudio: DockviewTheme = { + name: 'visualStudio', + className: 'dockview-theme-vs', +}; + +export const themeAbyss: DockviewTheme = { + name: 'abyss', + className: 'dockview-theme-abyss', +}; + +export const themeDracula: DockviewTheme = { + name: 'dracula', + className: 'dockview-theme-dracula', +}; + +export const themeReplit: DockviewTheme = { + name: 'replit', + className: 'dockview-theme-replit', + gap: 10, +}; + +export const themeAbyssSpaced: DockviewTheme = { + name: 'abyssSpaced', + className: 'dockview-theme-abyss-spaced', + gap: 10, + dndOverlayMounting: 'absolute', + includeHeaderWhenHoverOverContent: true, +}; + +export const themeLightSpaced: DockviewTheme = { + name: 'lightSpaced', + className: 'dockview-theme-light-spaced', + gap: 10, + dndOverlayMounting: 'absolute', + includeHeaderWhenHoverOverContent: true, +}; diff --git a/packages/dockview-core/src/index.ts b/packages/dockview-core/src/index.ts index 9764f658a..d9f66a787 100644 --- a/packages/dockview-core/src/index.ts +++ b/packages/dockview-core/src/index.ts @@ -64,6 +64,7 @@ export { } from './dockview/framework'; export * from './dockview/options'; +export * from './dockview/theme'; export * from './dockview/dockviewPanel'; export { DefaultTab } from './dockview/components/tab/defaultTab'; export { diff --git a/packages/dockview-core/src/splitview/splitview.scss b/packages/dockview-core/src/splitview/splitview.scss index adf09b368..047b382cf 100644 --- a/packages/dockview-core/src/splitview/splitview.scss +++ b/packages/dockview-core/src/splitview/splitview.scss @@ -116,6 +116,7 @@ -moz-user-select: none; // Firefox -ms-user-select: none; // IE 10 and IE 11 touch-action: none; + background-color: var(--dv-sash-color, transparent); &:not(.disabled):active { transition: background-color 0.1s ease-in-out; diff --git a/packages/dockview-core/src/splitview/splitview.ts b/packages/dockview-core/src/splitview/splitview.ts index 77fbb9cf5..c511d54f6 100644 --- a/packages/dockview-core/src/splitview/splitview.ts +++ b/packages/dockview-core/src/splitview/splitview.ts @@ -219,6 +219,8 @@ export class Splitview { set margin(value: number) { this._margin = value; + + toggleClass(this.element, 'dv-splitview-has-margin', value !== 0); } constructor( diff --git a/packages/dockview-core/src/theme.scss b/packages/dockview-core/src/theme.scss index 5eb3f7442..ee28b9565 100644 --- a/packages/dockview-core/src/theme.scss +++ b/packages/dockview-core/src/theme.scss @@ -1,17 +1,29 @@ +@import 'theme/_sash-handle-mixin'; +@import 'theme/_drop-target-static-mixin'; +@import 'theme/_space-mixin'; + @mixin dockview-theme-core-mixin { --dv-paneview-active-outline-color: dodgerblue; --dv-tabs-and-actions-container-font-size: 13px; --dv-tabs-and-actions-container-height: 35px; --dv-drag-over-background-color: rgba(83, 89, 93, 0.5); - --dv-drag-over-border-color: white; + --dv-drag-over-border-color: transparent; --dv-tabs-container-scrollbar-color: #888; --dv-icon-hover-background-color: rgba(90, 93, 94, 0.31); --dv-floating-box-shadow: 8px 8px 8px 0px rgba(83, 89, 93, 0.5); --dv-overlay-z-index: 999; + // + + --dv-tab-font-size: inherit; + --dv-border-radius: 0px; + --dv-tab-margin: 0; + --dv-sash-color: transparent; + --dv-active-sash-color: transparent; } @mixin dockview-theme-dark-mixin { @include dockview-theme-core-mixin(); + @include dockview-drop-target-no-travel(); // --dv-group-view-background-color: #1e1e1e; @@ -35,6 +47,8 @@ @mixin dockview-theme-light-mixin { @include dockview-theme-core-mixin(); + @include dockview-drop-target-no-travel(); + // --dv-group-view-background-color: white; // @@ -131,30 +145,49 @@ @mixin dockview-theme-abyss-mixin { @include dockview-theme-core-mixin(); + @include dockview-drop-target-no-travel(); + + --dv-color-abyss-dark: #000c18; + --dv-color-abyss: #10192c; + --dv-color-abyss-light: #1c1c2a; + --dv-color-abyss-lighter: #2b2b4a; + --dv-color-abyss-accent: rgb(91, 30, 207); + + --dv-color-abyss-primary-text: white; + --dv-color-abyss-secondary-text: rgb(148, 151, 169); + // - --dv-group-view-background-color: #000c18; + --dv-group-view-background-color: var(--dv-color-abyss-dark); // - --dv-tabs-and-actions-container-background-color: #1c1c2a; + --dv-tabs-and-actions-container-background-color: var( + --dv-color-abyss-light + ); // - --dv-activegroup-visiblepanel-tab-background-color: #000c18; - --dv-activegroup-hiddenpanel-tab-background-color: #10192c; - --dv-inactivegroup-visiblepanel-tab-background-color: #000c18; - --dv-inactivegroup-hiddenpanel-tab-background-color: #10192c; - --dv-tab-divider-color: #2b2b4a; + --dv-activegroup-visiblepanel-tab-background-color: var( + --dv-color-abyss-dark + ); + --dv-activegroup-hiddenpanel-tab-background-color: var(--dv-color-abyss); + --dv-inactivegroup-visiblepanel-tab-background-color: var( + --dv-color-abyss-dark + ); + --dv-inactivegroup-hiddenpanel-tab-background-color: var(--dv-color-abyss); + --dv-tab-divider-color: var(--dv-color-abyss-lighter); // --dv-activegroup-visiblepanel-tab-color: white; --dv-activegroup-hiddenpanel-tab-color: rgba(255, 255, 255, 0.5); --dv-inactivegroup-visiblepanel-tab-color: rgba(255, 255, 255, 0.5); --dv-inactivegroup-hiddenpanel-tab-color: rgba(255, 255, 255, 0.25); // - --dv-separator-border: #2b2b4a; - --dv-paneview-header-border-color: #2b2b4a; + --dv-separator-border: var(--dv-color-abyss-lighter); + --dv-paneview-header-border-color: var(--dv-color-abyss-lighter); --dv-paneview-active-outline-color: #596f99; } @mixin dockview-theme-dracula-mixin { @include dockview-theme-core-mixin(); + @include dockview-drop-target-no-travel(); + // --dv-group-view-background-color: #282a36; // @@ -229,10 +262,17 @@ } @mixin dockview-design-replit-mixin { + @include dockview-drop-target-no-travel(); + .dv-resize-container:has(> .dv-groupview) { border-radius: 8px; } + .dv-resize-container { + border-radius: 10px !important; + border: none; + } + .dv-groupview { overflow: hidden; border-radius: 10px; @@ -266,59 +306,16 @@ border: 1px solid transparent; } } - - .dv-vertical > .dv-sash-container > .dv-sash { - &:not(.disabled) { - &::after { - content: ''; - height: 4px; - width: 40px; - border-radius: 2px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background-color: var(--dv-separator-handle-background-color); - position: absolute; - } - - &:hover { - &::after { - background-color: var( - --dv-separator-handle-hover-background-color - ); - } - } - } - } - - .dv-horizontal > .dv-sash-container > .dv-sash { - &:not(.disabled) { - &::after { - content: ''; - height: 40px; - width: 4px; - border-radius: 2px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background-color: var(--dv-separator-handle-background-color); - position: absolute; - } - - &:hover { - &::after { - background-color: var( - --dv-separator-handle-hover-background-color - ); - } - } - } - } } .dockview-theme-replit { @include dockview-theme-core-mixin(); @include dockview-design-replit-mixin(); + @include dockview-design-handle-mixin(); + + padding: 10px; + background-color: #ebeced; + // --dv-group-view-background-color: #ebeced; // @@ -339,6 +336,115 @@ --dv-paneview-header-border-color: rgb(51, 51, 51); ///// - --dv-separator-handle-background-color: #cfd1d3; - --dv-separator-handle-hover-background-color: #babbbb; + --dv-sash-color: #cfd1d3; + --dv-active-sash-color: #babbbb; +} + +.dockview-theme-abyss-spaced { + @include dockview-theme-core-mixin(); + @include dockview-design-space-mixin(); + + // stylesheet + --dv-color-abyss-dark: rgb(11, 6, 17); + --dv-color-abyss: #16121f; + --dv-color-abyss-light: #201d2b; + --dv-color-abyss-lighter: #2a2837; + --dv-color-abyss-accent: rgb(91, 30, 207); + --dv-color-abyss-primary-text: white; + --dv-color-abyss-secondary-text: rgb(148, 151, 169); + + // + --dv-drag-over-border: 2px solid var(--dv-color-abyss-accent); + --dv-drag-over-background-color: ''; + // + + // + --dv-group-view-background-color: var(--dv-color-abyss-dark); + // + --dv-tabs-and-actions-container-background-color: var(--dv-color-abyss); + // + --dv-activegroup-visiblepanel-tab-background-color: var( + --dv-color-abyss-lighter + ); + --dv-activegroup-hiddenpanel-tab-background-color: var( + --dv-color-abyss-light + ); + --dv-inactivegroup-visiblepanel-tab-background-color: var( + --dv-color-abyss-lighter + ); + --dv-inactivegroup-hiddenpanel-tab-background-color: var( + --dv-color-abyss-light + ); + --dv-tab-divider-color: transparent; + // + --dv-activegroup-visiblepanel-tab-color: var(--dv-color-abyss-primary-text); + --dv-activegroup-hiddenpanel-tab-color: var( + --dv-color-abyss-secondary-text + ); + --dv-inactivegroup-visiblepanel-tab-color: var( + --dv-color-abyss-primary-text + ); + --dv-inactivegroup-hiddenpanel-tab-color: var( + --dv-color-abyss-secondary-text + ); + // + --dv-separator-border: transparent; + --dv-paneview-header-border-color: rgb(51, 51, 51); + + ///// + --dv-active-sash-color: var(--dv-color-abyss-accent); + // + --dv-floating-box-shadow: 8px 8px 8px 0px rgba(0, 0, 0, 0.5); + + padding: 10px; + background-color: var(--dv-color-abyss-dark); + + .dv-resize-container { + .dv-groupview { + border: 2px solid var(--dv-color-abyss-dark); + } + } +} + +.dockview-theme-light-spaced { + @include dockview-theme-core-mixin(); + @include dockview-design-space-mixin(); + + // + --dv-drag-over-border: 2px solid rgb(91, 30, 207); + --dv-drag-over-background-color: ''; + // + + // + --dv-group-view-background-color: #f6f5f9; + // + --dv-tabs-and-actions-container-background-color: white; + // + --dv-activegroup-visiblepanel-tab-background-color: #ededf0; + --dv-activegroup-hiddenpanel-tab-background-color: #f9f9fa; + --dv-inactivegroup-visiblepanel-tab-background-color: #ededf0; + --dv-inactivegroup-hiddenpanel-tab-background-color: #f9f9fa; + --dv-tab-divider-color: transparent; + // + --dv-activegroup-visiblepanel-tab-color: rgb(104, 107, 130); + --dv-activegroup-hiddenpanel-tab-color: rgb(148, 151, 169); + --dv-inactivegroup-visiblepanel-tab-color: rgb(104, 107, 130); + --dv-inactivegroup-hiddenpanel-tab-color: rgb(148, 151, 169); + // + --dv-separator-border: transparent; + --dv-paneview-header-border-color: rgb(51, 51, 51); + + ///// + --dv-active-sash-color: rgb(91, 30, 207); + // + --dv-floating-box-shadow: 8px 8px 8px 0px rgba(0, 0, 0, 0.1); + + padding: 10px; + background-color: #f6f5f9; + + .dv-resize-container { + .dv-groupview { + border: 2px solid rgb(255, 255, 255, 0.1); + } + } } diff --git a/packages/dockview-core/src/theme/_drop-target-static-mixin.scss b/packages/dockview-core/src/theme/_drop-target-static-mixin.scss new file mode 100644 index 000000000..b2e0e4ba7 --- /dev/null +++ b/packages/dockview-core/src/theme/_drop-target-static-mixin.scss @@ -0,0 +1,10 @@ +@mixin dockview-drop-target-no-travel { + .dv-drop-target-container { + .dv-drop-target-anchor { + &.dv-drop-target-anchor-container-changed { + opacity: 0; + transition: none; + } + } + } +} diff --git a/packages/dockview-core/src/theme/_sash-handle-mixin.scss b/packages/dockview-core/src/theme/_sash-handle-mixin.scss new file mode 100644 index 000000000..31e5822da --- /dev/null +++ b/packages/dockview-core/src/theme/_sash-handle-mixin.scss @@ -0,0 +1,53 @@ +@mixin dockview-design-handle-mixin { + .dv-vertical > .dv-sash-container > .dv-sash { + background-color: transparent; + + &:not(.disabled) { + &::after { + content: ''; + height: 4px; + width: 40px; + border-radius: 2px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: var(--dv-sash-color); + position: absolute; + } + + &:hover, + &:active { + background-color: transparent; + &::after { + background-color: var(--dv-active-sash-color); + } + } + } + } + + .dv-horizontal > .dv-sash-container > .dv-sash { + background-color: transparent; + + &:not(.disabled) { + &::after { + content: ''; + height: 40px; + width: 4px; + border-radius: 2px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: var(--dv-sash-color); + position: absolute; + } + + &:hover, + &:active { + background-color: transparent; + &::after { + background-color: var(--dv-active-sash-color); + } + } + } + } +} diff --git a/packages/dockview-core/src/theme/_space-mixin.scss b/packages/dockview-core/src/theme/_space-mixin.scss new file mode 100644 index 000000000..b2e84cdbd --- /dev/null +++ b/packages/dockview-core/src/theme/_space-mixin.scss @@ -0,0 +1,52 @@ +@mixin dockview-design-space-mixin { + --dv-tab-font-size: 12px; + --dv-border-radius: 20px; + --dv-tab-margin: 0.5rem 0.25rem; + --dv-tabs-and-actions-container-height: 44px; + + + --dv-border-radius + + .dv-resize-container:has(> .dv-groupview) { + border-radius: 8px; + } + + .dv-sash { + border-radius: 4px; + } + + .dv-drop-target-anchor { + border-radius: calc(var(--dv-border-radius) / 4); + &.dv-drop-target-content { + border-radius: var(--dv-border-radius); + } + } + + .dv-resize-container { + border-radius: var(--dv-border-radius) !important; + border: none; + } + + .dv-groupview { + border-radius: var(--dv-border-radius); + + .dv-tabs-and-actions-container { + padding: 0px calc(var(--dv-border-radius) / 2); + + .dv-tab { + border-radius: 8px; + + .dv-svg { + height: 8px; + width: 8px; + } + } + } + + .dv-content-container { + background-color: var( + --dv-tabs-and-actions-container-background-color + ); + } + } +} diff --git a/packages/dockview/src/svg.tsx b/packages/dockview/src/svg.tsx index 76143411a..eccf52bb6 100644 --- a/packages/dockview/src/svg.tsx +++ b/packages/dockview/src/svg.tsx @@ -7,7 +7,7 @@ export const CloseButton = () => ( viewBox="0 0 28 28" aria-hidden={'false'} focusable={false} - className="dockview-svg" + className="dv-svg" > @@ -21,7 +21,7 @@ export const ExpandMore = () => { viewBox="0 0 24 15" aria-hidden={'false'} focusable={false} - className="dockview-svg" + className="dv-svg" > diff --git a/packages/docs/docs/overview/getStarted/theme.mdx b/packages/docs/docs/overview/getStarted/theme.mdx index 2e89eefe6..6240340b9 100644 --- a/packages/docs/docs/overview/getStarted/theme.mdx +++ b/packages/docs/docs/overview/getStarted/theme.mdx @@ -7,7 +7,9 @@ title: Theme import { CSSVariablesTable, ThemeTable } from '@site/src/components/cssVariables'; -Theming is controlled through CSS and is highly customizable. + +Dockview components accept a `theme` property which is highly customizable, the theme is largly controlled through CSS however some properties can only be adjusted +by direct editing variables of the `theme` object. Firstly, you should import `dockview.css`: @@ -38,7 +40,7 @@ To use a `dockview` theme the CSS must encapsulate the component. The current li :::info -The source code for all themes can be found [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss). +The source code for all themes can be found [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss) and the associated CSS [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss). ::: ## Customizing Theme diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.scss b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.scss index 57549c075..2f7a940fc 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.scss +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.scss @@ -11,6 +11,7 @@ &:hover { border-radius: 2px; + color: var(--dv-activegroup-visiblepanel-tab-color); background-color: var(--dv-icon-hover-background-color); } } diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx index e1ccaef0f..a69632ebf 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx @@ -5,6 +5,7 @@ import { IDockviewPanelHeaderProps, IDockviewPanelProps, DockviewApi, + DockviewTheme, } from 'dockview'; import * as React from 'react'; import './app.scss'; @@ -80,6 +81,7 @@ const components = { ); }, nested: (props: IDockviewPanelProps) => { + const theme = React.useContext(ThemeContext); return ( ); }, @@ -141,7 +143,9 @@ const WatermarkComponent = () => { return
custom watermark
; }; -const DockviewDemo = (props: { theme?: string }) => { +const ThemeContext = React.createContext(undefined); + +const DockviewDemo = (props: { theme?: DockviewTheme }) => { const [logLines, setLogLines] = React.useState< { text: string; timestamp?: Date; backgroundColor?: string }[] >([]); @@ -380,18 +384,22 @@ const DockviewDemo = (props: { theme?: string }) => { }} > - + + + diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/controls.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/controls.tsx index 63032b5f4..c9fd5e19f 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/controls.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/controls.tsx @@ -81,7 +81,7 @@ export const RightControls = (props: IDockviewHeaderActionsProps) => { alignItems: 'center', padding: '0px 8px', height: '100%', - color: 'var(--dv-activegroup-visiblepanel-tab-color)', + color: 'var(--dv-activegroup-hiddenpanel-tab-color)', }} > {props.isGroupActive && } diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx index 40e57b2fa..ec16135ed 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx @@ -151,11 +151,20 @@ export const GridActions = (props: { props.api?.addGroup(); }; - const [gap, setGap] = React.useState(0); + // const [gap, setGap] = React.useState(undefined); - React.useEffect(() => { - props.api?.setGap(gap); - }, [gap, props.api]); + const [overlayMode, setOverlayMode] = React.useState(false); + + // React.useEffect(() => { + // if (!props.api) { + // return; + // } + // if (typeof gap === 'number') { + // props.api.setGap(gap); + // } else { + // setGap(props.api.gap); + // } + // }, [gap, props.api]); return (
@@ -191,6 +200,23 @@ export const GridActions = (props: { Use Custom Watermark + {/* + + */} @@ -204,7 +230,7 @@ export const GridActions = (props: { Reset -
+ {/*
Grid Gap setGap(Number(event.target.value))} /> -
+
*/}
); }; diff --git a/packages/docs/src/components/frameworkSpecific.css b/packages/docs/src/components/frameworkSpecific.css index a92d63d29..9a03811a8 100644 --- a/packages/docs/src/components/frameworkSpecific.css +++ b/packages/docs/src/components/frameworkSpecific.css @@ -1,13 +1,16 @@ .DropdownMenuContent { /* min-width: 220px; */ - background-color: rgba(255, 255, 255, 0.1); + background-color: var(--ifm-dropdown-background-color); + color: var(--ifm-color-primary); + border: var(--ifm-dropdown-border); border-radius: 6px; padding: 5px; box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2); animation-duration: 400ms; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); will-change: transform, opacity; + z-index: 99; } .DropdownMenuContent[data-side='top'], .DropdownMenuSubContent[data-side='top'] { @@ -39,25 +42,33 @@ display: flex; align-items: center; justify-content: space-between; - width: 100px; - height: 25px; + width: 120px; + /* height: 25px; */ padding: 4px 8px; - font-size: 13px; + font-size: 1rem; + font-weight: normal; cursor: pointer; + + color: var(--ifm-menu-color); + + &:hover { + background-color: var(--ifm-hover-overlay); + } } .framework-menu-item-select { display: flex; align-items: center; justify-content: space-between; - width: 120px; + width: 130px; height: 35px; padding: 4px 8px; border-radius: 6px; - font-size: 13px; - background-color: rgba(255, 255, 255, 0.1); + font-size: 1rem; + font-weight: normal; cursor: pointer; - border: 1px solid rgba(0,0,0, 0.1); + + border: 1px solid rgba(60, 60, 66,0.5); } @keyframes slideUpAndFade { diff --git a/packages/docs/src/components/frameworkSpecific.tsx b/packages/docs/src/components/frameworkSpecific.tsx index 65ae1d549..14d8ea830 100644 --- a/packages/docs/src/components/frameworkSpecific.tsx +++ b/packages/docs/src/components/frameworkSpecific.tsx @@ -1,6 +1,8 @@ import BrowserOnly from '@docusaurus/BrowserOnly'; import { DockviewEmitter } from 'dockview'; import * as React from 'react'; +import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; +import useBaseUrl from '@docusaurus/useBaseUrl'; import './frameworkSpecific.css'; export interface FrameworkDescriptor { @@ -51,8 +53,7 @@ export function useActiveFramework(): [ return [option, setter]; } -import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; -import useBaseUrl from '@docusaurus/useBaseUrl'; + const FrameworkSelector1 = () => { const [activeFramework, setActiveFramework] = useActiveFramework(); diff --git a/packages/docs/src/components/ui/codeSandboxButton.scss b/packages/docs/src/components/ui/codeSandboxButton.scss index 8c31be3b8..5fda1291b 100644 --- a/packages/docs/src/components/ui/codeSandboxButton.scss +++ b/packages/docs/src/components/ui/codeSandboxButton.scss @@ -28,7 +28,7 @@ } } -.dockview-svg { +.dv-svg { display: inline-block; fill: currentcolor; line-height: 1; diff --git a/packages/docs/src/components/ui/codeSandboxButton.tsx b/packages/docs/src/components/ui/codeSandboxButton.tsx index a68aa34d7..c2c6e02ff 100644 --- a/packages/docs/src/components/ui/codeSandboxButton.tsx +++ b/packages/docs/src/components/ui/codeSandboxButton.tsx @@ -17,7 +17,7 @@ const createSvgElementFromPath = (params: { width={params.width} viewBox={params.viewbox} focusable={false} - className={'dockview-svg'} + className={'dv-svg'} > @@ -54,7 +54,7 @@ export const CodeSandboxButton = (props: { { return ( { const JavascriptIcon = (props: { height: number; width: number }) => { return ( { return (
{ diff --git a/packages/docs/src/config/theme.config.ts b/packages/docs/src/config/theme.config.ts index 2e8b9741f..0946720df 100644 --- a/packages/docs/src/config/theme.config.ts +++ b/packages/docs/src/config/theme.config.ts @@ -1,33 +1,54 @@ +import { + themeAbyss, + themeDark, + themeDracula, + themeAbyssSpaced, + themeLightSpaced, + themeLight, + themeReplit, + themeVisualStudio, +} from 'dockview'; + export const themeConfig = [ { - id: 'dockview-theme-dark', + id: themeDark, key: '**[dockview-theme-dark](/demo?theme=dockview-theme-dark)**', text: '', }, { - id: 'dockview-theme-light', + id: themeLight, key: '**[dockview-theme-light](/demo?theme=dockview-theme-light)**', text: '', }, { - id: 'dockview-theme-vs', + id: themeVisualStudio, key: '**[dockview-theme-vs](/demo?theme=dockview-theme-vs)**', text: 'Based on [Visual Studio](https://visualstudio.microsoft.com)', }, { - id: 'dockview-theme-abyss', + id: themeAbyss, key: '**[dockview-theme-abyss](/demo?theme=dockview-theme-abyss)**', text: 'Based on [Visual Studio Code](https://code.visualstudio.com/docs/getstarted/themes) abyss theme', }, { - id: 'dockview-theme-dracula', + id: themeDracula, key: '**[dockview-theme-dracula](/demo?theme=dockview-theme-dracula)**', text: 'Based on [Visual Studio Code](https://code.visualstudio.com/docs/getstarted/themes) dracula theme', }, { - id: 'dockview-theme-replit', + id: themeReplit, key: '**[dockview-theme-replit](/demo?theme=dockview-theme-replit)**', text: 'Based on [Replit](https://replit.com)', }, + { + id: themeLightSpaced, + key: '**[dockview-theme-replit](/demo?theme=dockview-theme-kraken)**', + text: '', + }, + { + id: themeAbyssSpaced, + key: '**[dockview-theme-replit](/demo?theme=dockview-theme-kraken)**', + text: '', + }, ]; diff --git a/packages/docs/src/css/custom.scss b/packages/docs/src/css/custom.scss index 9d446add3..0ab665892 100644 --- a/packages/docs/src/css/custom.scss +++ b/packages/docs/src/css/custom.scss @@ -11,10 +11,10 @@ /* You can override the default Infima variables here. */ :root { - --ifm-font-family-base: "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, - BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, - sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, - Noto Color Emoji; + --ifm-font-family-base: 'IBM Plex Sans', ui-sans-serif, system-ui, + -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, + Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, + Segoe UI Symbol, Noto Color Emoji; --ifm-font-weight-bold: 600; @@ -36,6 +36,9 @@ --ifm-color-primary: black; + --ifm-dropdown-background-color: white; + --ifm-dropdown-border: 1px solid var(--ifm-color-primary-darkest); + --ifm-navbar-link-color: white; --ifm-navbar-link-hover-color: white; @@ -54,15 +57,18 @@ } /* --ifm-color-primary: #0c111d; */ - --ifm-color-primary: #25c2a0; - --ifm-color-primary-dark: #21af90; - --ifm-color-primary-darker: #1fa588; - --ifm-color-primary-darkest: #1a8870; - --ifm-color-primary-light: #29d5b0; - --ifm-color-primary-lighter: #32d8b4; - --ifm-color-primary-lightest: #4fddbf; + --ifm-color-primary: #98a2b3; + --ifm-color-primary-dark: #828a99; + --ifm-color-primary-darker: #6a707c; + --ifm-color-primary-darkest: #474b53; + --ifm-color-primary-light: #acb7ca; + --ifm-color-primary-lighter: #bcc9df; + --ifm-color-primary-lightest: #d2e1fa; --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); + --ifm-dropdown-background-color: #373d4b; + --ifm-dropdown-border: 1px solid var(--ifm-color-primary-darkest); + --dv-docs-markdown-text-color: #cdced8; } diff --git a/packages/docs/src/pages/demo.tsx b/packages/docs/src/pages/demo.tsx index cab33d7ef..d04d76f60 100644 --- a/packages/docs/src/pages/demo.tsx +++ b/packages/docs/src/pages/demo.tsx @@ -3,11 +3,29 @@ import Layout from '@theme/Layout'; import { themeConfig } from '../config/theme.config'; import ExampleFrame from '../components/ui/exampleFrame'; import BrowserOnly from '@docusaurus/BrowserOnly'; +import { DockviewTheme, themeAbyss } from 'dockview'; + +const updateTheme = (theme: DockviewTheme) => { + const urlParams = new URLSearchParams(window.location.search); + + urlParams.set('theme', theme.name); + + const newUrl = window.location.pathname + '?' + urlParams.toString(); + + window.history.pushState({ path: newUrl }, '', newUrl); +}; const ThemeToggle: React.FC = () => { - const [theme, setTheme] = React.useState( - new URLSearchParams(location.search).get('theme') ?? themeConfig[3].id - ); + const [theme, setTheme] = React.useState(themeAbyss); + + React.useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + const themeName = urlParams.get('theme'); + const newTheme = + themeConfig.find((c) => c.id.name === themeName)?.id ?? themeAbyss; + setTheme(newTheme); + updateTheme(newTheme); + }, []); return ( <> @@ -16,20 +34,48 @@ const ThemeToggle: React.FC = () => { height: '40px', display: 'flex', alignItems: 'center', + padding: '0px 15px', }} > - { - const url = new URL(window.location.href); - url.searchParams.set('theme', event.target.value); - window.location.href = url.toString(); + const theme = themeConfig.find( + (theme) => theme.id.name === event.target.value + ).id; + setTheme(theme); + updateTheme(theme); }} - value={theme} + value={theme.name} > {themeConfig.map((theme) => { - return ; + return ( + + ); })} - + */}
); } + +import { + DropdownMenu, + DropdownMenuItem, + DropdownMenuContent, + DropdownMenuTrigger, +} from '@radix-ui/react-dropdown-menu'; + +const ThemeSelector = (props: { + options: string[]; + value: string; + onChanged: (value: string) => void; +}) => { + const ref = React.useRef(null); + + return ( +
+ { + if (!open) { + return; + } + + if (!ref.current) { + return; + } + + requestAnimationFrame(() => { + const el = ref.current!.querySelector( + `[data-dropdown-menu-value="${props.value}"]` + ); + if (el) { + (el as HTMLElement).focus(); + } + }); + }} + > + +
+ {props.value} +
+
+ + {props.options.map((option) => { + return ( + props.onChanged(option)} + className="DropdownMenuItem" + > +
+ {option} + + {option === props.value ? '✓' : ''} + +
+
+ ); + })} +
+
+
+ ); +}; From 7a6b2cb26da3e6349a3e060c27d7d847f6834ebe Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 25 Feb 2025 21:05:38 +0000 Subject: [PATCH 72/83] feat: tab panel overflow dropdown --- .../__mocks__/mockDockviewPanelModel.ts | 8 +- .../dockview/dockviewComponent.spec.ts | 6 +- .../dockview/dockviewGroupPanelModel.spec.ts | 7 +- .../dockview/components/titlebar/tabs.scss | 158 ++++++++----- .../components/titlebar/{tabs.tsx => tabs.ts} | 214 ++++++++++++++---- .../components/titlebar/tabsContainer.ts | 5 + .../src/dockview/dockviewPanelModel.ts | 30 ++- .../dockview-core/src/dockview/framework.ts | 4 +- packages/dockview-core/src/dockview/types.ts | 7 +- packages/dockview-core/src/dom.ts | 22 ++ .../src/gridview/baseComponentGridview.ts | 3 + .../dockview-core/src/theme/_space-mixin.scss | 27 ++- packages/dockview-vue/src/utils.ts | 5 +- packages/dockview/src/dockview/defaultTab.tsx | 3 +- .../dockview/src/dockview/reactHeaderPart.ts | 11 +- 15 files changed, 360 insertions(+), 150 deletions(-) rename packages/dockview-core/src/dockview/components/titlebar/{tabs.tsx => tabs.ts} (53%) diff --git a/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelModel.ts b/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelModel.ts index 79e21b88a..35a301025 100644 --- a/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelModel.ts +++ b/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelModel.ts @@ -1,11 +1,12 @@ import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel'; import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; import { - GroupPanelPartInitParameters, + TabPartInitParameters, IContentRenderer, ITabRenderer, } from '../../dockview/types'; import { PanelUpdateEvent } from '../../panel/types'; +import { TabLocation } from '../../dockview/framework'; export class DockviewPanelModelMock implements IDockviewPanelModel { constructor( @@ -17,8 +18,11 @@ export class DockviewPanelModelMock implements IDockviewPanelModel { // } + copyTabComponent(tabLocation: TabLocation): ITabRenderer { + return this.tab; + } - init(params: GroupPanelPartInitParameters): void { + init(params: TabPartInitParameters): void { // } diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 9df09dc2f..ad6088d09 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -2453,17 +2453,17 @@ describe('dockviewComponent', () => { const group = dockview.getGroupPanel('panel2')!.api.group; const viewQuery = group.element.querySelectorAll( - '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab' + '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-panel > .dv-tabs-container > .dv-tab' ); expect(viewQuery.length).toBe(2); const viewQuery2 = group.element.querySelectorAll( - '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab > .dv-default-tab' + '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-panel > .dv-tabs-container > .dv-tab > .dv-default-tab' ); expect(viewQuery2.length).toBe(1); const viewQuery3 = group.element.querySelectorAll( - '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab > .panel-tab-part-panel2' + '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-panel > .dv-tabs-container > .dv-tab > .panel-tab-part-panel2' ); expect(viewQuery3.length).toBe(1); }); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts index 19b811c4f..d87413609 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts @@ -24,6 +24,7 @@ import { createOffsetDragOverEvent } from '../__test_utils__/utils'; import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer'; import { Emitter } from '../../events'; import { fromPartial } from '@total-typescript/shoehorn'; +import { TabLocation } from '../../dockview/framework'; enum GroupChangeKind2 { ADD_PANEL, @@ -36,12 +37,16 @@ class TestModel implements IDockviewPanelModel { readonly contentComponent: string; readonly tab: ITabRenderer; - constructor(id: string) { + constructor(readonly id: string) { this.content = new TestHeaderPart(id); this.contentComponent = id; this.tab = new TestContentPart(id); } + copyTabComponent(tabLocation: TabLocation): ITabRenderer { + return new TestHeaderPart(this.id); + } + update(event: PanelUpdateEvent): void { // } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss index d797c5f06..df15a09e5 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss @@ -1,23 +1,93 @@ -.dv-tabs-container { - display: flex; - overflow-x: overlay; - overflow-y: hidden; +.dv-tabs-panel { + overflow: hidden; - scrollbar-width: thin; // firefox + &.dv-horizontal { + .dv-tabs-container { + .dv-tab { + &:last-child { + margin-right: 0; + } - &::-webkit-scrollbar { - height: 3px; + &:not(:nth-last-child(1)) { + margin-left: 0; + } + + &:not(:first-child)::before { + content: ' '; + position: absolute; + top: 0; + left: 0; + z-index: 5; + pointer-events: none; + background-color: var(--dv-tab-divider-color); + width: 1px; + height: 100%; + } + } + } } - /* Track */ - &::-webkit-scrollbar-track { - background: transparent; + .dv-tabs-container { + display: flex; + overflow: hidden; + scrollbar-width: thin; // firefox + + &::-webkit-scrollbar { + height: 3px; + } + + /* Track */ + &::-webkit-scrollbar-track { + background: transparent; + } + + /* Handle */ + &::-webkit-scrollbar-thumb { + background: var(--dv-tabs-container-scrollbar-color); + } + + .dv-tab { + -webkit-user-drag: element; + outline: none; + padding: 0.25rem 0.5rem; + cursor: pointer; + position: relative; + box-sizing: border-box; + font-size: var(--dv-tab-font-size); + margin: var(--dv-tab-margin); + } } - /* Handle */ - &::-webkit-scrollbar-thumb { - background: var(--dv-tabs-container-scrollbar-color); + .dv-tabs-overflow-dropdown-default { + background-color: var( + --dv-activegroup-hiddenpanel-tab-background-color + ); + height: 100%; + color: var(--dv-activegroup-hiddenpanel-tab-color); + border-left: 1px solid var(--dv-tab-divider-color); + + margin: var(--dv-tab-margin); + display: flex; + align-items: center; + flex-shrink: 0; + padding: 0.25rem 0.5rem; + cursor: pointer; + + > span { + padding-left: 0.25rem; + } + + > svg { + transform: rotate(90deg); + } } +} + +.dv-tabs-overflow-container { + flex-direction: column; + height: unset; + border: 1px solid var(--dv-tab-divider-color); + background-color: var(--dv-group-view-background-color); .dv-tab { -webkit-user-drag: element; @@ -26,58 +96,24 @@ cursor: pointer; position: relative; box-sizing: border-box; - font-size: var(-dv-tab-font-size); + font-size: var(--dv-tab-font-size); margin: var(--dv-tab-margin); - &:first-child { - margin-right: 0; - } - - &:not(:nth-last-child(1)) { - margin-left: 0; - } - - &:not(:first-child)::before { - content: ' '; - position: absolute; - top: 0; - left: 0; - z-index: 5; - pointer-events: none; - background-color: var(--dv-tab-divider-color); - width: 1px; - height: 100%; + &:not(:last-child) { + border-bottom: 1px solid var(--dv-tab-divider-color); } } - &.dv-tabs-overflow-container { - flex-direction: column; - height: unset; - - .dv-tab { - height: var(--dv-tabs-and-actions-container-height); - } - - .dv-active-tab { - background-color: var( - --dv-activegroup-visiblepanel-tab-background-color - ); - color: var(--dv-activegroup-visiblepanel-tab-color); - } - .dv-inactive-tab { - background-color: var( - --dv-activegroup-hiddenpanel-tab-background-color - ); - color: var(--dv-activegroup-hiddenpanel-tab-color); - } - } -} - -.dv-tabs-panel { - .dv-tabs-overflow-handle { - height: 100%; - width: 15px; - flex-shrink: 0; - background-color: red; + .dv-active-tab { + background-color: var( + --dv-activegroup-visiblepanel-tab-background-color + ); + color: var(--dv-activegroup-visiblepanel-tab-color); + } + .dv-inactive-tab { + background-color: var( + --dv-activegroup-hiddenpanel-tab-background-color + ); + color: var(--dv-activegroup-hiddenpanel-tab-color); } } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.tsx b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts similarity index 53% rename from packages/dockview-core/src/dockview/components/titlebar/tabs.tsx rename to packages/dockview-core/src/dockview/components/titlebar/tabs.ts index a477531a4..659061b89 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.tsx +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts @@ -1,11 +1,17 @@ import { getPanelData } from '../../../dnd/dataTransfer'; -import { OverflowObserver } from '../../../dom'; +import { + isChildEntirelyVisibleWithinParent, + OverflowObserver, + toggleClass, +} from '../../../dom'; import { addDisposableListener, Emitter, Event } from '../../../events'; import { CompositeDisposable, Disposable, IValueDisposable, + MutableDisposable, } from '../../../lifecycle'; +import { createChevronRightButton } from '../../../svg'; import { DockviewComponent } from '../../dockviewComponent'; import { DockviewGroupPanel } from '../../dockviewGroupPanel'; import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel'; @@ -13,14 +19,38 @@ import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel'; import { Tab } from '../tab/tab'; import { TabDragEvent, TabDropIndexEvent } from './tabsContainer'; +type DropdownElement = { + element: HTMLElement; + update: (params: { tabs: number }) => void; + dispose?: () => void; +}; + +function createDropdownElementHandle(): DropdownElement { + const el = document.createElement('div'); + el.className = 'dv-tabs-overflow-dropdown-default'; + + const text = document.createElement('span'); + text.textContent = ``; + const icon = createChevronRightButton(); + el.appendChild(icon); + el.appendChild(text); + + return { + element: el, + update: (params: { tabs: number }) => { + text.textContent = `${params.tabs}`; + }, + }; +} + export class Tabs extends CompositeDisposable { private readonly _element: HTMLElement; private readonly _tabsList: HTMLElement; private tabs: IValueDisposable[] = []; private selectedIndex = -1; - private _hasOverflow = false; - private _dropdownAnchor: HTMLElement | null = null; + + private readonly _dropdownDisposable = new MutableDisposable(); private readonly _onTabDragStart = new Emitter(); readonly onTabDragStart: Event = this._onTabDragStart.event; @@ -33,6 +63,9 @@ export class Tabs extends CompositeDisposable { readonly onWillShowOverlay: Event = this._onWillShowOverlay.event; + private dropdownPart: DropdownElement | null = null; + private _overflowTabs: string[] = []; + get element(): HTMLElement { return this._element; } @@ -52,7 +85,7 @@ export class Tabs extends CompositeDisposable { super(); this._element = document.createElement('div'); - this._element.className = 'dv-tabs-panel'; + this._element.className = 'dv-tabs-panel dv-horizontal'; this._element.style.display = 'flex'; this._element.style.overflow = 'auto'; this._tabsList = document.createElement('div'); @@ -62,12 +95,17 @@ export class Tabs extends CompositeDisposable { const observer = new OverflowObserver(this._tabsList); this.addDisposables( + this._dropdownDisposable, + this._onWillShowOverlay, + this._onDrop, + this._onTabDragStart, observer, observer.onDidChange((event) => { const hasOverflow = event.hasScrollX || event.hasScrollY; - if (this._hasOverflow !== hasOverflow) { - this.toggleDropdown(hasOverflow); - } + this.toggleDropdown({ reset: !hasOverflow }); + }), + addDisposableListener(this._tabsList, 'scroll', () => { + this.toggleDropdown({ reset: false }); }), addDisposableListener(this.element, 'pointerdown', (event) => { if (event.defaultPrevented) { @@ -103,10 +141,27 @@ export class Tabs extends CompositeDisposable { } setActivePanel(panel: IDockviewPanel): void { - this.tabs.forEach((tab) => { + let runningWidth = 0; + + for (const tab of this.tabs) { const isActivePanel = panel.id === tab.value.panel.id; tab.value.setActive(isActivePanel); - }); + + if (isActivePanel) { + const element = tab.value.element; + const parentElement = element.parentElement!; + + if ( + runningWidth < parentElement.scrollLeft || + runningWidth + element.clientWidth > + parentElement.scrollLeft + parentElement.clientWidth + ) { + parentElement.scrollLeft = runningWidth; + } + } + + runningWidth += tab.value.element.clientWidth; + } } openPanel(panel: IDockviewPanel, index: number = this.tabs.length): void { @@ -120,7 +175,11 @@ export class Tabs extends CompositeDisposable { tab.onDragStart((event) => { this._onTabDragStart.fire({ nativeEvent: event, panel }); }), - tab.onChanged((event) => { + tab.onPointerDown((event) => { + if (event.defaultPrevented) { + return; + } + const isFloatingGroupsEnabled = !this.accessor.options.disableFloatingGroups; @@ -149,14 +208,12 @@ export class Tabs extends CompositeDisposable { return; } - const isLeftClick = event.button === 0; - - if (!isLeftClick || event.defaultPrevented) { - return; - } - - if (this.group.activePanel !== panel) { - this.group.model.openPanel(panel); + switch (event.button) { + case 0: // left click or touch + if (this.group.activePanel !== panel) { + this.group.model.openPanel(panel); + } + break; } }), tab.onDrop((event) => { @@ -218,48 +275,105 @@ export class Tabs extends CompositeDisposable { } } - private toggleDropdown(show: boolean): void { - this._hasOverflow = show; + private toggleDropdown(options: { reset: boolean }): void { + const tabs = options.reset + ? [] + : this.tabs + .filter( + (tab) => + !isChildEntirelyVisibleWithinParent( + tab.value.element, + this._tabsList + ) + ) + .map((x) => x.value.panel.id); - if (this._dropdownAnchor) { - this._dropdownAnchor.remove(); - this._dropdownAnchor = null; - } + this._overflowTabs = tabs; - if (!show) { + if (this._overflowTabs.length > 0 && this.dropdownPart) { + this.dropdownPart.update({ tabs: tabs.length }); return; } - this._dropdownAnchor = document.createElement('div'); - this._dropdownAnchor.className = 'dv-tabs-overflow-handle'; + if (this._overflowTabs.length === 0) { + this._dropdownDisposable.dispose(); + return; + } - this.element.appendChild(this._dropdownAnchor); + const root = document.createElement('div'); + root.className = 'dv-tabs-overflow-dropdown-root'; - addDisposableListener(this._dropdownAnchor, 'click', (event) => { - const el = document.createElement('div'); - el.style.overflow = 'auto'; - el.className = - 'dv-tabs-and-actions-container dv-tabs-container dv-tabs-overflow-container'; + const part = createDropdownElementHandle(); + part.update({ tabs: tabs.length }); - this.tabs.map((tab) => { - const child = tab.value.element.cloneNode(true); + this.dropdownPart = part; - const wrapper = document.createElement('div'); + root.appendChild(part.element); + this.element.appendChild(root); - wrapper.addEventListener('mousedown', () => { - this.accessor.popupService.close(); - tab.value.element.scrollIntoView(); - tab.value.panel.api.setActive(); + this._dropdownDisposable.value = new CompositeDisposable( + Disposable.from(() => { + root.remove(); + this.dropdownPart?.dispose?.(); + this.dropdownPart = null; + }), + addDisposableListener( + root, + 'pointerdown', + (event) => { + event.preventDefault(); + }, + { capture: true } + ), + addDisposableListener(root, 'click', (event) => { + const el = document.createElement('div'); + el.style.overflow = 'auto'; + el.className = 'dv-tabs-overflow-container'; + + this.tabs + .filter((tab) => + this._overflowTabs.includes(tab.value.panel.id) + ) + .map((tab) => { + const panelObject = this.group.panels.find( + (panel) => panel === tab.value.panel + )!; + + const tabComponent = + panelObject.view.createTabRenderer( + 'headerOverflow' + ); + + const child = tabComponent.element; + + const wrapper = document.createElement('div'); + toggleClass(wrapper, 'dv-tab', true); + toggleClass( + wrapper, + 'dv-active-tab', + panelObject.api.isActive + ); + toggleClass( + wrapper, + 'dv-inactive-tab', + !panelObject.api.isActive + ); + + wrapper.addEventListener('mousedown', () => { + this.accessor.popupService.close(); + tab.value.element.scrollIntoView(); + tab.value.panel.api.setActive(); + }); + wrapper.appendChild(child); + + el.appendChild(wrapper); + }); + + this.accessor.popupService.openPopover(el, { + x: event.clientX, + y: event.clientY, }); - wrapper.appendChild(child); - - el.appendChild(wrapper); - }); - - this.accessor.popupService.openPopover(el, { - x: event.clientX, - y: event.clientY, - }); - }); + }) + ); } } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index 602bc67fb..e41952087 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -140,6 +140,7 @@ export class TabsContainer this._element.appendChild(this.rightActionsContainer); this.addDisposables( + this.tabs, this._onWillShowOverlay, this._onDrop, this._onGroupDragStart, @@ -171,6 +172,10 @@ export class TabsContainer this.voidContainer.element, 'pointerdown', (event) => { + if (event.defaultPrevented) { + return; + } + const isFloatingGroupsEnabled = !this.accessor.options.disableFloatingGroups; diff --git a/packages/dockview-core/src/dockview/dockviewPanelModel.ts b/packages/dockview-core/src/dockview/dockviewPanelModel.ts index 777717bad..903fcc095 100644 --- a/packages/dockview-core/src/dockview/dockviewPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanelModel.ts @@ -4,27 +4,29 @@ import { IContentRenderer, ITabRenderer, } from './types'; -import { DockviewGroupPanel } from './dockviewGroupPanel'; import { IDisposable } from '../lifecycle'; import { IDockviewComponent } from './dockviewComponent'; import { PanelUpdateEvent } from '../panel/types'; +import { TabLocation } from './framework'; export interface IDockviewPanelModel extends IDisposable { readonly contentComponent: string; readonly tabComponent?: string; readonly content: IContentRenderer; readonly tab: ITabRenderer; - readonly newTab: ITabRenderer; update(event: PanelUpdateEvent): void; layout(width: number, height: number): void; init(params: GroupPanelPartInitParameters): void; - updateParentGroup(group: DockviewGroupPanel, isPanelVisible: boolean): void; + createTabRenderer(tabLocation: TabLocation): ITabRenderer; } export class DockviewPanelModel implements IDockviewPanelModel { private readonly _content: IContentRenderer; private readonly _tab: ITabRenderer; + private _params: GroupPanelPartInitParameters | undefined; + private _updateEvent: PanelUpdateEvent | undefined; + get content(): IContentRenderer { return this._content; } @@ -43,21 +45,23 @@ export class DockviewPanelModel implements IDockviewPanelModel { this._tab = this.createTabComponent(this.id, tabComponent); } - get newTab() { + createTabRenderer(tabLocation: TabLocation): ITabRenderer { const cmp = this.createTabComponent(this.id, this.tabComponent); + if (this._params) { + cmp.init({ ...this._params, tabLocation }); + } + if (this._updateEvent) { + cmp.update?.(this._updateEvent); + } + return cmp; } init(params: GroupPanelPartInitParameters): void { - this.content.init(params); - this.tab.init(params); - } + this._params = params; - updateParentGroup( - _group: DockviewGroupPanel, - _isPanelVisible: boolean - ): void { - // noop + this.content.init(params); + this.tab.init({ ...params, tabLocation: 'header' }); } layout(width: number, height: number): void { @@ -65,6 +69,8 @@ export class DockviewPanelModel implements IDockviewPanelModel { } update(event: PanelUpdateEvent): void { + this._updateEvent = event; + this.content.update?.(event); this.tab.update?.(event); } diff --git a/packages/dockview-core/src/dockview/framework.ts b/packages/dockview-core/src/dockview/framework.ts index bd6ccaff5..1ed239b46 100644 --- a/packages/dockview-core/src/dockview/framework.ts +++ b/packages/dockview-core/src/dockview/framework.ts @@ -11,9 +11,11 @@ export interface IGroupPanelBaseProps containerApi: DockviewApi; } +export type TabLocation = 'header' | 'headerOverflow'; + export type IDockviewPanelHeaderProps< T extends { [index: string]: any } = any -> = IGroupPanelBaseProps; +> = IGroupPanelBaseProps & { tabLocation: TabLocation }; export type IDockviewPanelProps = IGroupPanelBaseProps; diff --git a/packages/dockview-core/src/dockview/types.ts b/packages/dockview-core/src/dockview/types.ts index 3b2c8f112..5706f0721 100644 --- a/packages/dockview-core/src/dockview/types.ts +++ b/packages/dockview-core/src/dockview/types.ts @@ -4,6 +4,7 @@ import { DockviewApi } from '../api/component.api'; import { Optional } from '../types'; import { IDockviewGroupPanel } from './dockviewGroupPanel'; import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer'; +import { TabLocation } from './framework'; export interface HeaderPartInitParameters { title: string; @@ -34,10 +35,14 @@ export interface IWatermarkRenderer init: (params: WatermarkRendererInitParameters) => void; } +export interface TabPartInitParameters extends GroupPanelPartInitParameters { + tabLocation: TabLocation; +} + export interface ITabRenderer extends Optional, RendererMethodOptionalList> { readonly element: HTMLElement; - init(parameters: GroupPanelPartInitParameters): void; + init(parameters: TabPartInitParameters): void; } export interface IContentRenderer diff --git a/packages/dockview-core/src/dom.ts b/packages/dockview-core/src/dom.ts index d61b1cacb..3dc9000a9 100644 --- a/packages/dockview-core/src/dom.ts +++ b/packages/dockview-core/src/dom.ts @@ -357,3 +357,25 @@ export class Classnames { } } } + +export function isChildEntirelyVisibleWithinParent( + child: HTMLElement, + parent: HTMLElement +): boolean { + // + const childPosition = getDomNodePagePosition(child); + const parentPosition = getDomNodePagePosition(parent); + + if (childPosition.left < parentPosition.left) { + return false; + } + + if ( + childPosition.left + childPosition.width > + parentPosition.left + parentPosition.width + ) { + return false; + } + + return true; +} diff --git a/packages/dockview-core/src/gridview/baseComponentGridview.ts b/packages/dockview-core/src/gridview/baseComponentGridview.ts index 93779f5a0..2ef754396 100644 --- a/packages/dockview-core/src/gridview/baseComponentGridview.ts +++ b/packages/dockview-core/src/gridview/baseComponentGridview.ts @@ -11,6 +11,9 @@ import { Classnames } from '../dom'; const nextLayoutId = sequentialNumberGenerator(); +/** + * A direction in which a panel can be moved or placed relative to another panel. + */ export type Direction = 'left' | 'right' | 'above' | 'below' | 'within'; export function toTarget(direction: Direction): Position { diff --git a/packages/dockview-core/src/theme/_space-mixin.scss b/packages/dockview-core/src/theme/_space-mixin.scss index b2e84cdbd..2482b6841 100644 --- a/packages/dockview-core/src/theme/_space-mixin.scss +++ b/packages/dockview-core/src/theme/_space-mixin.scss @@ -4,8 +4,7 @@ --dv-tab-margin: 0.5rem 0.25rem; --dv-tabs-and-actions-container-height: 44px; - - --dv-border-radius + --dv-border-radius: 20px; .dv-resize-container:has(> .dv-groupview) { border-radius: 8px; @@ -27,20 +26,26 @@ border: none; } + .dv-tabs-overflow-container, + .dv-tabs-overflow-dropdown-default { + border-radius: 8px; + height: unset !important; + } + + .dv-tab { + border-radius: 8px; + + .dv-svg { + height: 8px; + width: 8px; + } + } + .dv-groupview { border-radius: var(--dv-border-radius); .dv-tabs-and-actions-container { padding: 0px calc(var(--dv-border-radius) / 2); - - .dv-tab { - border-radius: 8px; - - .dv-svg { - height: 8px; - width: 8px; - } - } } .dv-content-container { diff --git a/packages/dockview-vue/src/utils.ts b/packages/dockview-vue/src/utils.ts index 45c23fafd..3b4c4fa71 100644 --- a/packages/dockview-vue/src/utils.ts +++ b/packages/dockview-vue/src/utils.ts @@ -2,7 +2,6 @@ import type { DockviewApi, DockviewGroupPanel, DockviewPanelApi, - GroupPanelPartInitParameters, IContentRenderer, IDockviewPanelHeaderProps, IGroupHeaderProps, @@ -12,6 +11,7 @@ import type { IWatermarkRenderer, PanelUpdateEvent, Parameters, + TabPartInitParameters, WatermarkRendererInitParameters, } from 'dockview-core'; import { @@ -121,7 +121,7 @@ export class VueRenderer private _api: DockviewPanelApi | undefined; private _containerApi: DockviewApi | undefined; - init(parameters: GroupPanelPartInitParameters): void { + init(parameters: TabPartInitParameters): void { this._api = parameters.api; this._containerApi = parameters.containerApi; @@ -129,6 +129,7 @@ export class VueRenderer params: parameters.params, api: parameters.api, containerApi: parameters.containerApi, + tabLocation: parameters.tabLocation, }; this._renderDisposable?.dispose(); diff --git a/packages/dockview/src/dockview/defaultTab.tsx b/packages/dockview/src/dockview/defaultTab.tsx index e12e61465..f8164560f 100644 --- a/packages/dockview/src/dockview/defaultTab.tsx +++ b/packages/dockview/src/dockview/defaultTab.tsx @@ -35,6 +35,7 @@ export const DockviewDefaultTab: React.FunctionComponent< onPointerDown, onPointerUp, onPointerLeave, + tabLocation, ...rest }) => { const title = useTitle(api); @@ -96,7 +97,7 @@ export const DockviewDefaultTab: React.FunctionComponent< className="dv-default-tab" > {title} - {!hideClose && ( + {!hideClose && tabLocation !== 'headerOverflow' && (
; + private part?: ReactPart; get element(): HTMLElement { return this._element; @@ -17,7 +17,7 @@ export class ReactPanelHeaderPart implements ITabRenderer { constructor( public readonly id: string, - private readonly component: React.FunctionComponent, + private readonly component: React.FunctionComponent, private readonly reactPortalStore: ReactPortalStore ) { this._element = document.createElement('div'); @@ -30,7 +30,7 @@ export class ReactPanelHeaderPart implements ITabRenderer { //noop } - public init(parameters: GroupPanelPartInitParameters): void { + public init(parameters: TabPartInitParameters): void { this.part = new ReactPart( this.element, this.reactPortalStore, @@ -39,6 +39,7 @@ export class ReactPanelHeaderPart implements ITabRenderer { params: parameters.params, api: parameters.api, containerApi: parameters.containerApi, + tabLocation: parameters.tabLocation, } ); } From cfe37766a95160e534189579a6f91e65f830bac7 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 1 Mar 2025 21:04:06 +0000 Subject: [PATCH 73/83] feat: scrollbars --- .../__mocks__/mockDockviewPanelModel.ts | 2 +- .../dockview/dockviewComponent.spec.ts | 14 +- .../dockview/dockviewGroupPanelModel.spec.ts | 2 +- .../titlebar/tabOverflowControl.scss | 19 ++ .../components/titlebar/tabOverflowControl.ts | 25 ++ .../dockview/components/titlebar/tabs.scss | 73 ++---- .../src/dockview/components/titlebar/tabs.ts | 222 ++++++------------ .../components/titlebar/tabsContainer.scss | 4 + .../components/titlebar/tabsContainer.ts | 117 ++++++++- .../src/dockview/dockviewComponent.scss | 4 +- packages/dockview-core/src/scrollbar.scss | 28 +++ packages/dockview-core/src/scrollbar.ts | 131 +++++++++++ .../__tests__/dockview/defaultTab.spec.tsx | 7 + 13 files changed, 432 insertions(+), 216 deletions(-) create mode 100644 packages/dockview-core/src/dockview/components/titlebar/tabOverflowControl.scss create mode 100644 packages/dockview-core/src/dockview/components/titlebar/tabOverflowControl.ts create mode 100644 packages/dockview-core/src/scrollbar.scss create mode 100644 packages/dockview-core/src/scrollbar.ts diff --git a/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelModel.ts b/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelModel.ts index 35a301025..2a43a1796 100644 --- a/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelModel.ts +++ b/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelModel.ts @@ -18,7 +18,7 @@ export class DockviewPanelModelMock implements IDockviewPanelModel { // } - copyTabComponent(tabLocation: TabLocation): ITabRenderer { + createTabRenderer(tabLocation: TabLocation): ITabRenderer { return this.tab; } diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index ad6088d09..d84a723e4 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -133,11 +133,15 @@ describe('dockviewComponent', () => { }, className: 'test-a test-b', }); - expect(dockview.element.className).toBe('test-a test-b dockview-theme-abyss'); + expect(dockview.element.className).toBe( + 'test-a test-b dockview-theme-abyss' + ); dockview.updateOptions({ className: 'test-b test-c' }); - expect(dockview.element.className).toBe('dockview-theme-abyss test-b test-c'); + expect(dockview.element.className).toBe( + 'dockview-theme-abyss test-b test-c' + ); }); describe('memory leakage', () => { @@ -2453,17 +2457,17 @@ describe('dockviewComponent', () => { const group = dockview.getGroupPanel('panel2')!.api.group; const viewQuery = group.element.querySelectorAll( - '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-panel > .dv-tabs-container > .dv-tab' + '.dv-groupview > .dv-tabs-and-actions-container > .dv-scrollable > .dv-tabs-container > .dv-tab' ); expect(viewQuery.length).toBe(2); const viewQuery2 = group.element.querySelectorAll( - '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-panel > .dv-tabs-container > .dv-tab > .dv-default-tab' + '.dv-groupview > .dv-tabs-and-actions-container > .dv-scrollable > .dv-tabs-container > .dv-tab > .dv-default-tab' ); expect(viewQuery2.length).toBe(1); const viewQuery3 = group.element.querySelectorAll( - '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-panel > .dv-tabs-container > .dv-tab > .panel-tab-part-panel2' + '.dv-groupview > .dv-tabs-and-actions-container > .dv-scrollable > .dv-tabs-container > .dv-tab > .panel-tab-part-panel2' ); expect(viewQuery3.length).toBe(1); }); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts index d87413609..719c26ca0 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts @@ -43,7 +43,7 @@ class TestModel implements IDockviewPanelModel { this.tab = new TestContentPart(id); } - copyTabComponent(tabLocation: TabLocation): ITabRenderer { + createTabRenderer(tabLocation: TabLocation): ITabRenderer { return new TestHeaderPart(this.id); } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabOverflowControl.scss b/packages/dockview-core/src/dockview/components/titlebar/tabOverflowControl.scss new file mode 100644 index 000000000..2d87ce7ca --- /dev/null +++ b/packages/dockview-core/src/dockview/components/titlebar/tabOverflowControl.scss @@ -0,0 +1,19 @@ +.dv-tabs-overflow-dropdown-default { + height: 100%; + color: var(--dv-activegroup-hiddenpanel-tab-color); + + margin: var(--dv-tab-margin); + display: flex; + align-items: center; + flex-shrink: 0; + padding: 0.25rem 0.5rem; + cursor: pointer; + + > span { + padding-left: 0.25rem; + } + + > svg { + transform: rotate(90deg); + } +} diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabOverflowControl.ts b/packages/dockview-core/src/dockview/components/titlebar/tabOverflowControl.ts new file mode 100644 index 000000000..1e27c661e --- /dev/null +++ b/packages/dockview-core/src/dockview/components/titlebar/tabOverflowControl.ts @@ -0,0 +1,25 @@ +import { createChevronRightButton } from '../../../svg'; + +export type DropdownElement = { + element: HTMLElement; + update: (params: { tabs: number }) => void; + dispose?: () => void; +}; + +export function createDropdownElementHandle(): DropdownElement { + const el = document.createElement('div'); + el.className = 'dv-tabs-overflow-dropdown-default'; + + const text = document.createElement('span'); + text.textContent = ``; + const icon = createChevronRightButton(); + el.appendChild(icon); + el.appendChild(text); + + return { + element: el, + update: (params: { tabs: number }) => { + text.textContent = `${params.tabs}`; + }, + }; +} diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss index df15a09e5..5b9e7487f 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss @@ -1,6 +1,5 @@ -.dv-tabs-panel { +.dv-tabs-container { overflow: hidden; - &.dv-horizontal { .dv-tabs-container { .dv-tab { @@ -27,62 +26,38 @@ } } - .dv-tabs-container { - display: flex; - overflow: hidden; - scrollbar-width: thin; // firefox + display: flex; + height: 100%; + overflow: hidden; + scrollbar-width: thin; // firefox - &::-webkit-scrollbar { - height: 3px; - } - - /* Track */ - &::-webkit-scrollbar-track { - background: transparent; - } - - /* Handle */ - &::-webkit-scrollbar-thumb { - background: var(--dv-tabs-container-scrollbar-color); - } - - .dv-tab { - -webkit-user-drag: element; - outline: none; - padding: 0.25rem 0.5rem; - cursor: pointer; - position: relative; - box-sizing: border-box; - font-size: var(--dv-tab-font-size); - margin: var(--dv-tab-margin); - } + &::-webkit-scrollbar { + height: 3px; } - .dv-tabs-overflow-dropdown-default { - background-color: var( - --dv-activegroup-hiddenpanel-tab-background-color - ); - height: 100%; - color: var(--dv-activegroup-hiddenpanel-tab-color); - border-left: 1px solid var(--dv-tab-divider-color); + /* Track */ + &::-webkit-scrollbar-track { + background: transparent; + } - margin: var(--dv-tab-margin); - display: flex; - align-items: center; - flex-shrink: 0; + /* Handle */ + &::-webkit-scrollbar-thumb { + background: var(--dv-tabs-container-scrollbar-color); + } + + .dv-tab { + -webkit-user-drag: element; + outline: none; padding: 0.25rem 0.5rem; cursor: pointer; - - > span { - padding-left: 0.25rem; - } - - > svg { - transform: rotate(90deg); - } + position: relative; + box-sizing: border-box; + font-size: var(--dv-tab-font-size); + margin: var(--dv-tab-margin); } } + .dv-tabs-overflow-container { flex-direction: column; height: unset; diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts index 659061b89..b8a6d5499 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts @@ -2,7 +2,6 @@ import { getPanelData } from '../../../dnd/dataTransfer'; import { isChildEntirelyVisibleWithinParent, OverflowObserver, - toggleClass, } from '../../../dom'; import { addDisposableListener, Emitter, Event } from '../../../events'; import { @@ -11,7 +10,7 @@ import { IValueDisposable, MutableDisposable, } from '../../../lifecycle'; -import { createChevronRightButton } from '../../../svg'; +import { Scrollbar } from '../../../scrollbar'; import { DockviewComponent } from '../../dockviewComponent'; import { DockviewGroupPanel } from '../../dockviewGroupPanel'; import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel'; @@ -19,38 +18,14 @@ import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel'; import { Tab } from '../tab/tab'; import { TabDragEvent, TabDropIndexEvent } from './tabsContainer'; -type DropdownElement = { - element: HTMLElement; - update: (params: { tabs: number }) => void; - dispose?: () => void; -}; - -function createDropdownElementHandle(): DropdownElement { - const el = document.createElement('div'); - el.className = 'dv-tabs-overflow-dropdown-default'; - - const text = document.createElement('span'); - text.textContent = ``; - const icon = createChevronRightButton(); - el.appendChild(icon); - el.appendChild(text); - - return { - element: el, - update: (params: { tabs: number }) => { - text.textContent = `${params.tabs}`; - }, - }; -} - export class Tabs extends CompositeDisposable { private readonly _element: HTMLElement; private readonly _tabsList: HTMLElement; + private readonly _observerDisposable = new MutableDisposable(); - private tabs: IValueDisposable[] = []; + private _tabs: IValueDisposable[] = []; private selectedIndex = -1; - - private readonly _dropdownDisposable = new MutableDisposable(); + private _showTabsOverflowControl = false; private readonly _onTabDragStart = new Emitter(); readonly onTabDragStart: Event = this._onTabDragStart.event; @@ -63,50 +38,79 @@ export class Tabs extends CompositeDisposable { readonly onWillShowOverlay: Event = this._onWillShowOverlay.event; - private dropdownPart: DropdownElement | null = null; - private _overflowTabs: string[] = []; + private readonly _onOverflowTabsChange = new Emitter<{ + tabs: string[]; + reset: boolean; + }>(); + readonly onOverflowTabsChange = this._onOverflowTabsChange.event; + + get showTabsOverflowControl(): boolean { + return this._showTabsOverflowControl; + } + + set showTabsOverflowControl(value: boolean) { + if (this._showTabsOverflowControl == value) { + return; + } + + this._showTabsOverflowControl = value; + + if (value) { + const observer = new OverflowObserver(this._tabsList); + + this._observerDisposable.value = new CompositeDisposable( + observer, + observer.onDidChange((event) => { + const hasOverflow = event.hasScrollX || event.hasScrollY; + this.toggleDropdown({ reset: !hasOverflow }); + }), + addDisposableListener(this._tabsList, 'scroll', () => { + this.toggleDropdown({ reset: false }); + }) + ); + } + } get element(): HTMLElement { return this._element; } get panels(): string[] { - return this.tabs.map((_) => _.value.panel.id); + return this._tabs.map((_) => _.value.panel.id); } get size(): number { - return this.tabs.length; + return this._tabs.length; + } + + get tabs(): Tab[] { + return this._tabs.map((_) => _.value); } constructor( private readonly group: DockviewGroupPanel, - private readonly accessor: DockviewComponent + private readonly accessor: DockviewComponent, + options: { + showTabsOverflowControl: boolean; + } ) { super(); - this._element = document.createElement('div'); - this._element.className = 'dv-tabs-panel dv-horizontal'; - this._element.style.display = 'flex'; - this._element.style.overflow = 'auto'; this._tabsList = document.createElement('div'); - this._tabsList.className = 'dv-tabs-container'; - this._element.appendChild(this._tabsList); + this._tabsList.className = 'dv-tabs-container dv-horizontal'; - const observer = new OverflowObserver(this._tabsList); + this.showTabsOverflowControl = options.showTabsOverflowControl; + + const scrollbar = new Scrollbar(this._tabsList); + this._element = scrollbar.element; this.addDisposables( - this._dropdownDisposable, + this._onOverflowTabsChange, + this._observerDisposable, + scrollbar, this._onWillShowOverlay, this._onDrop, this._onTabDragStart, - observer, - observer.onDidChange((event) => { - const hasOverflow = event.hasScrollX || event.hasScrollY; - this.toggleDropdown({ reset: !hasOverflow }); - }), - addDisposableListener(this._tabsList, 'scroll', () => { - this.toggleDropdown({ reset: false }); - }), addDisposableListener(this.element, 'pointerdown', (event) => { if (event.defaultPrevented) { return; @@ -119,31 +123,31 @@ export class Tabs extends CompositeDisposable { } }), Disposable.from(() => { - for (const { value, disposable } of this.tabs) { + for (const { value, disposable } of this._tabs) { disposable.dispose(); value.dispose(); } - this.tabs = []; + this._tabs = []; }) ); } indexOf(id: string): number { - return this.tabs.findIndex((tab) => tab.value.panel.id === id); + return this._tabs.findIndex((tab) => tab.value.panel.id === id); } isActive(tab: Tab): boolean { return ( this.selectedIndex > -1 && - this.tabs[this.selectedIndex].value === tab + this._tabs[this.selectedIndex].value === tab ); } setActivePanel(panel: IDockviewPanel): void { let runningWidth = 0; - for (const tab of this.tabs) { + for (const tab of this._tabs) { const isActivePanel = panel.id === tab.value.panel.id; tab.value.setActive(isActivePanel); @@ -164,8 +168,8 @@ export class Tabs extends CompositeDisposable { } } - openPanel(panel: IDockviewPanel, index: number = this.tabs.length): void { - if (this.tabs.find((tab) => tab.value.panel.id === panel.id)) { + openPanel(panel: IDockviewPanel, index: number = this._tabs.length): void { + if (this._tabs.find((tab) => tab.value.panel.id === panel.id)) { return; } const tab = new Tab(panel, this.accessor, this.group); @@ -219,7 +223,7 @@ export class Tabs extends CompositeDisposable { tab.onDrop((event) => { this._onDrop.fire({ event: event.nativeEvent, - index: this.tabs.findIndex((x) => x.value === tab), + index: this._tabs.findIndex((x) => x.value === tab), }); }), tab.onWillShowOverlay((event) => { @@ -242,7 +246,7 @@ export class Tabs extends CompositeDisposable { delete(id: string): void { const index = this.indexOf(id); - const tabToRemove = this.tabs.splice(index, 1)[0]; + const tabToRemove = this._tabs.splice(index, 1)[0]; const { value, disposable } = tabToRemove; @@ -253,9 +257,9 @@ export class Tabs extends CompositeDisposable { private addTab( tab: IValueDisposable, - index: number = this.tabs.length + index: number = this._tabs.length ): void { - if (index < 0 || index > this.tabs.length) { + if (index < 0 || index > this._tabs.length) { throw new Error('invalid location'); } @@ -264,10 +268,10 @@ export class Tabs extends CompositeDisposable { this._tabsList.children[index] ); - this.tabs = [ - ...this.tabs.slice(0, index), + this._tabs = [ + ...this._tabs.slice(0, index), tab, - ...this.tabs.slice(index), + ...this._tabs.slice(index), ]; if (this.selectedIndex < 0) { @@ -278,7 +282,7 @@ export class Tabs extends CompositeDisposable { private toggleDropdown(options: { reset: boolean }): void { const tabs = options.reset ? [] - : this.tabs + : this._tabs .filter( (tab) => !isChildEntirelyVisibleWithinParent( @@ -288,92 +292,6 @@ export class Tabs extends CompositeDisposable { ) .map((x) => x.value.panel.id); - this._overflowTabs = tabs; - - if (this._overflowTabs.length > 0 && this.dropdownPart) { - this.dropdownPart.update({ tabs: tabs.length }); - return; - } - - if (this._overflowTabs.length === 0) { - this._dropdownDisposable.dispose(); - return; - } - - const root = document.createElement('div'); - root.className = 'dv-tabs-overflow-dropdown-root'; - - const part = createDropdownElementHandle(); - part.update({ tabs: tabs.length }); - - this.dropdownPart = part; - - root.appendChild(part.element); - this.element.appendChild(root); - - this._dropdownDisposable.value = new CompositeDisposable( - Disposable.from(() => { - root.remove(); - this.dropdownPart?.dispose?.(); - this.dropdownPart = null; - }), - addDisposableListener( - root, - 'pointerdown', - (event) => { - event.preventDefault(); - }, - { capture: true } - ), - addDisposableListener(root, 'click', (event) => { - const el = document.createElement('div'); - el.style.overflow = 'auto'; - el.className = 'dv-tabs-overflow-container'; - - this.tabs - .filter((tab) => - this._overflowTabs.includes(tab.value.panel.id) - ) - .map((tab) => { - const panelObject = this.group.panels.find( - (panel) => panel === tab.value.panel - )!; - - const tabComponent = - panelObject.view.createTabRenderer( - 'headerOverflow' - ); - - const child = tabComponent.element; - - const wrapper = document.createElement('div'); - toggleClass(wrapper, 'dv-tab', true); - toggleClass( - wrapper, - 'dv-active-tab', - panelObject.api.isActive - ); - toggleClass( - wrapper, - 'dv-inactive-tab', - !panelObject.api.isActive - ); - - wrapper.addEventListener('mousedown', () => { - this.accessor.popupService.close(); - tab.value.element.scrollIntoView(); - tab.value.panel.api.setActive(); - }); - wrapper.appendChild(child); - - el.appendChild(wrapper); - }); - - this.accessor.popupService.openPopover(el, { - x: event.clientX, - y: event.clientY, - }); - }) - ); + this._onOverflowTabsChange.fire({ tabs, reset: options.reset }); } } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss index 14815f8bc..1ed918a1c 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss @@ -25,4 +25,8 @@ flex-grow: 1; cursor: grab; } + + .dv-right-actions-container { + display: flex; + } } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index e41952087..6a8b803e3 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -1,4 +1,9 @@ -import { IDisposable, CompositeDisposable } from '../../../lifecycle'; +import { + IDisposable, + CompositeDisposable, + Disposable, + MutableDisposable, +} from '../../../lifecycle'; import { addDisposableListener, Emitter, Event } from '../../../events'; import { Tab } from '../tab/tab'; import { DockviewGroupPanel } from '../../dockviewGroupPanel'; @@ -6,12 +11,13 @@ import { VoidContainer } from './voidContainer'; import { toggleClass } from '../../../dom'; import { IDockviewPanel } from '../../dockviewPanel'; import { DockviewComponent } from '../../dockviewComponent'; -import { - DockviewGroupPanelModel, - WillShowOverlayLocationEvent, -} from '../../dockviewGroupPanelModel'; +import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel'; import { getPanelData } from '../../../dnd/dataTransfer'; import { Tabs } from './tabs'; +import { + createDropdownElementHandle, + DropdownElement, +} from './tabOverflowControl'; export interface TabDropIndexEvent { readonly event: DragEvent; @@ -68,6 +74,10 @@ export class TabsContainer private _hidden = false; + private dropdownPart: DropdownElement | null = null; + private _overflowTabs: string[] = []; + private readonly _dropdownDisposable = new MutableDisposable(); + private readonly _onDrop = new Emitter(); readonly onDrop: Event = this._onDrop.event; @@ -129,7 +139,13 @@ export class TabsContainer this.preActionsContainer = document.createElement('div'); this.preActionsContainer.className = 'dv-pre-actions-container'; - this.tabs = new Tabs(group, accessor); + this.tabs = new Tabs(group, accessor, { + showTabsOverflowControl: false, + }); + + this.tabs.onOverflowTabsChange((event) => { + this.toggleDropdown(event); + }); this.voidContainer = new VoidContainer(this.accessor, this.group); @@ -287,4 +303,93 @@ export class TabsContainer private updateClassnames(): void { toggleClass(this._element, 'dv-single-tab', this.size === 1); } + + private toggleDropdown(options: { tabs: string[]; reset: boolean }): void { + const tabs = options.reset ? [] : options.tabs; + this._overflowTabs = tabs; + + if (this._overflowTabs.length > 0 && this.dropdownPart) { + this.dropdownPart.update({ tabs: tabs.length }); + return; + } + + if (this._overflowTabs.length === 0) { + this._dropdownDisposable.dispose(); + return; + } + + const root = document.createElement('div'); + root.className = 'dv-tabs-overflow-dropdown-root'; + + const part = createDropdownElementHandle(); + part.update({ tabs: tabs.length }); + + this.dropdownPart = part; + + root.appendChild(part.element); + this.rightActionsContainer.prepend(root); + + this._dropdownDisposable.value = new CompositeDisposable( + Disposable.from(() => { + root.remove(); + this.dropdownPart?.dispose?.(); + this.dropdownPart = null; + }), + addDisposableListener( + root, + 'pointerdown', + (event) => { + event.preventDefault(); + }, + { capture: true } + ), + addDisposableListener(root, 'click', (event) => { + const el = document.createElement('div'); + el.style.overflow = 'auto'; + el.className = 'dv-tabs-overflow-container'; + + this.tabs.tabs + .filter((tab) => this._overflowTabs.includes(tab.panel.id)) + .map((tab) => { + const panelObject = this.group.panels.find( + (panel) => panel === tab.panel + )!; + + const tabComponent = + panelObject.view.createTabRenderer( + 'headerOverflow' + ); + + const child = tabComponent.element; + + const wrapper = document.createElement('div'); + toggleClass(wrapper, 'dv-tab', true); + toggleClass( + wrapper, + 'dv-active-tab', + panelObject.api.isActive + ); + toggleClass( + wrapper, + 'dv-inactive-tab', + !panelObject.api.isActive + ); + + wrapper.addEventListener('mousedown', () => { + this.accessor.popupService.close(); + tab.element.scrollIntoView(); + tab.panel.api.setActive(); + }); + wrapper.appendChild(child); + + el.appendChild(wrapper); + }); + + this.accessor.popupService.openPopover(el, { + x: event.clientX, + y: event.clientY, + }); + }) + ); + } } diff --git a/packages/dockview-core/src/dockview/dockviewComponent.scss b/packages/dockview-core/src/dockview/dockviewComponent.scss index b08c0ada0..3340e976a 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.scss +++ b/packages/dockview-core/src/dockview/dockviewComponent.scss @@ -19,7 +19,7 @@ .dv-groupview { &.dv-active-group { > .dv-tabs-and-actions-container - > .dv-tabs-panel + > .dv-scrollable > .dv-tabs-container > .dv-tab { &.dv-active-tab { @@ -38,7 +38,7 @@ } &.dv-inactive-group { > .dv-tabs-and-actions-container - > .dv-tabs-panel + > .dv-scrollable > .dv-tabs-container > .dv-tab { &.dv-active-tab { diff --git a/packages/dockview-core/src/scrollbar.scss b/packages/dockview-core/src/scrollbar.scss new file mode 100644 index 000000000..1276b51d8 --- /dev/null +++ b/packages/dockview-core/src/scrollbar.scss @@ -0,0 +1,28 @@ +.dv-scrollable { + position: relative; + overflow: hidden; + + .dv-scrollbar-horizontal { + position: absolute; + bottom: 0px; + left: 0px; + height: 4px; + border-radius: 2px; + background-color: transparent; + transition-property: background-color; + transition-timing-function: ease-in-out; + transition-duration: 1s; + transition-delay: 0s; + } + + &:hover, + &.dv-scrollable-resizing, + &.dv-scrollable-scrolling { + .dv-scrollbar-horizontal { + background-color: var( + --dv-scrollbar-background-color, + rgba(255, 255, 255, 0.25) + ); + } + } +} diff --git a/packages/dockview-core/src/scrollbar.ts b/packages/dockview-core/src/scrollbar.ts new file mode 100644 index 000000000..535ebd8e2 --- /dev/null +++ b/packages/dockview-core/src/scrollbar.ts @@ -0,0 +1,131 @@ +import { toggleClass, watchElementResize } from './dom'; +import { addDisposableListener } from './events'; +import { CompositeDisposable } from './lifecycle'; +import { clamp } from './math'; + +export class Scrollbar extends CompositeDisposable { + private _element: HTMLElement; + private _horizontalScrollbar: HTMLElement; + private _scrollLeft: number = 0; + private _animationTimer: any; + static MouseWheelSpeed = 1; + + get element(): HTMLElement { + return this._element; + } + + constructor(private readonly scrollableElement: HTMLElement) { + super(); + + this._element = document.createElement('div'); + this._element.className = 'dv-scrollable'; + + this._horizontalScrollbar = document.createElement('div'); + this._horizontalScrollbar.className = 'dv-scrollbar-horizontal'; + + this.element.appendChild(scrollableElement); + this.element.appendChild(this._horizontalScrollbar); + + this.addDisposables( + addDisposableListener(this.element, 'wheel', (event) => { + this._scrollLeft += event.deltaY * Scrollbar.MouseWheelSpeed; + + this.calculateScrollbarStyles(); + }), + addDisposableListener( + this._horizontalScrollbar, + 'pointerdown', + (event) => { + event.preventDefault(); + + toggleClass(this.element, 'dv-scrollable-scrolling', true); + + const originalClientX = event.clientX; + const originalScrollLeft = this._scrollLeft; + + const onPointerMove = (event: PointerEvent) => { + const deltaX = event.clientX - originalClientX; + + const { clientWidth } = this.element; + const { scrollWidth } = this.scrollableElement; + const p = clientWidth / scrollWidth; + + this._scrollLeft = originalScrollLeft + deltaX / p; + this.calculateScrollbarStyles(); + }; + + const onEnd = () => { + toggleClass( + this.element, + 'dv-scrollable-scrolling', + false + ); + + document.removeEventListener( + 'pointermove', + onPointerMove + ); + document.removeEventListener('pointerup', onEnd); + document.removeEventListener('pointercancel', onEnd); + }; + + document.addEventListener('pointermove', onPointerMove); + document.addEventListener('pointerup', onEnd); + document.addEventListener('pointercancel', onEnd); + } + ), + addDisposableListener(this.element, 'scroll', () => { + this.calculateScrollbarStyles(); + }), + addDisposableListener(this.scrollableElement, 'scroll', () => { + this._scrollLeft = this.scrollableElement.scrollLeft; + this.calculateScrollbarStyles(); + }), + watchElementResize(this.element, () => { + toggleClass(this.element, 'dv-scrollable-resizing', true); + + if (this._animationTimer) { + clearTimeout(this._animationTimer); + } + + this._animationTimer = setTimeout(() => { + clearTimeout(this._animationTimer); + toggleClass(this.element, 'dv-scrollable-resizing', false); + }, 500); + + this.calculateScrollbarStyles(); + }) + ); + } + + private calculateScrollbarStyles(): void { + const { clientWidth } = this.element; + const { scrollWidth } = this.scrollableElement; + + const hasScrollbar = scrollWidth > clientWidth; + + if (hasScrollbar) { + const px = clientWidth * (clientWidth / scrollWidth); + this._horizontalScrollbar.style.width = `${px}px`; + + this._scrollLeft = clamp( + this._scrollLeft, + 0, + this.scrollableElement.scrollWidth - clientWidth + ); + + this.scrollableElement.scrollLeft = this._scrollLeft; + + const percentageComplete = + this._scrollLeft / (scrollWidth - clientWidth); + + this._horizontalScrollbar.style.left = `${ + (clientWidth - px) * percentageComplete + }px`; + } else { + this._horizontalScrollbar.style.width = `0px`; + this._horizontalScrollbar.style.left = `0px`; + this._scrollLeft = 0; + } + } +} diff --git a/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx b/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx index 93da0a000..85a8fe0ce 100644 --- a/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx +++ b/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx @@ -19,6 +19,7 @@ describe('defaultTab', () => { render( { render( { render( { render( { render( { render( { render( Date: Mon, 3 Mar 2025 20:47:54 +0000 Subject: [PATCH 74/83] chore: update sonarqube build version --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 96a4de786..8f11fa57c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: - run: npm run build - run: npm run test:cov - name: SonarCloud Scan - uses: sonarsource/sonarqube-scan-action@v4.1.0 + uses: sonarsource/sonarqube-scan-action@v5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} From 96d6947aa623783af6bf150a976dc1eb81f4a8c1 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 3 Mar 2025 20:53:59 +0000 Subject: [PATCH 75/83] feat: scrollbars --- .../__tests__/api/dockviewPanelApi.spec.ts | 3 + .../components/titlebar/tabsContainer.spec.ts | 14 ++++ .../dockview/dockviewGroupPanel.spec.ts | 3 + .../dockview/dockviewGroupPanelModel.spec.ts | 6 ++ .../__tests__/gridview/gridviewPanel.spec.ts | 1 + .../src/dockview/components/popupService.ts | 2 +- .../dockview/components/titlebar/tabs.scss | 10 +-- .../components/titlebar/tabsContainer.ts | 73 ++++++++++--------- .../src/dockview/dockviewComponent.ts | 4 + .../dockview-core/src/dockview/options.ts | 2 + packages/dockview-core/src/scrollbar.ts | 6 +- 11 files changed, 78 insertions(+), 46 deletions(-) diff --git a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts index 0bc24eaa5..d4255ab22 100644 --- a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts +++ b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts @@ -10,6 +10,7 @@ describe('groupPanelApi', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const panelMock = jest.fn(() => { @@ -50,6 +51,7 @@ describe('groupPanelApi', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupViewPanel = new DockviewGroupPanel( @@ -82,6 +84,7 @@ describe('groupPanelApi', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupViewPanel = new DockviewGroupPanel( diff --git a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts index bae8daa8d..bb79949cf 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts @@ -18,6 +18,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -71,6 +72,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const dropTargetContainer = document.createElement('div'); @@ -140,6 +142,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -203,6 +206,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -266,6 +270,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -334,6 +339,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -398,6 +404,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -464,6 +471,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), doSetGroupActive: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -520,6 +528,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), doSetGroupActive: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -571,6 +580,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -627,6 +637,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -694,6 +705,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -761,6 +773,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -824,6 +837,7 @@ describe('tabsContainer', () => { const cut = new TabsContainer( fromPartial({ options: {}, + onDidOptionsChange: jest.fn(), }), fromPartial({}) ); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts index eb2ab5320..1a01b1d30 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts @@ -16,6 +16,7 @@ describe('dockviewGroupPanel', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const options = fromPartial({}); const cut = new DockviewGroupPanel(accessor, 'test_id', options); @@ -39,6 +40,7 @@ describe('dockviewGroupPanel', () => { detatch: jest.fn(), }, doSetGroupActive: jest.fn(), + onDidOptionsChange: jest.fn(), }); const options = fromPartial({}); @@ -81,6 +83,7 @@ describe('dockviewGroupPanel', () => { detatch: jest.fn(), }), options: {}, + onDidOptionsChange: jest.fn(), }); const options = fromPartial({}); const cut = new DockviewGroupPanel(accessor, 'test_id', options); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts index 719c26ca0..6e24cd53f 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts @@ -270,6 +270,7 @@ describe('dockviewGroupPanelModel', () => { document.createElement('div'), fromPartial({}) ), + onDidOptionsChange: () => ({ dispose: jest.fn() }), }); groupview = new DockviewGroupPanel(dockview, 'groupview-1', options); @@ -651,6 +652,7 @@ describe('dockviewGroupPanelModel', () => { getPanel: jest.fn(), onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -713,6 +715,7 @@ describe('dockviewGroupPanelModel', () => { getPanel: jest.fn(), onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -809,6 +812,7 @@ describe('dockviewGroupPanelModel', () => { document.createElement('div'), fromPartial({}) ), + onDidOptionsChange: jest.fn(), }); const groupView = fromPartial({ @@ -875,6 +879,7 @@ describe('dockviewGroupPanelModel', () => { document.createElement('div'), fromPartial({}) ), + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -948,6 +953,7 @@ describe('dockviewGroupPanelModel', () => { document.createElement('div'), fromPartial({}) ), + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( diff --git a/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts b/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts index cfae5ea6b..18ed7d1d6 100644 --- a/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts +++ b/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts @@ -8,6 +8,7 @@ describe('gridviewPanel', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), } as any; }); diff --git a/packages/dockview-core/src/dockview/components/popupService.ts b/packages/dockview-core/src/dockview/components/popupService.ts index 1fb5a0996..58f0b9853 100644 --- a/packages/dockview-core/src/dockview/components/popupService.ts +++ b/packages/dockview-core/src/dockview/components/popupService.ts @@ -8,7 +8,7 @@ import { export class PopupService extends CompositeDisposable { private readonly _element: HTMLElement; private _active: HTMLElement | null = null; - private _activeDisposable = new MutableDisposable(); + private readonly _activeDisposable = new MutableDisposable(); constructor(private readonly root: HTMLElement) { super(); diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss index 5b9e7487f..279659fac 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss @@ -1,5 +1,9 @@ .dv-tabs-container { + display: flex; + height: 100%; overflow: hidden; + scrollbar-width: thin; // firefox + &.dv-horizontal { .dv-tabs-container { .dv-tab { @@ -26,11 +30,6 @@ } } - display: flex; - height: 100%; - overflow: hidden; - scrollbar-width: thin; // firefox - &::-webkit-scrollbar { height: 3px; } @@ -57,7 +56,6 @@ } } - .dv-tabs-overflow-container { flex-direction: column; height: unset; diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index 6a8b803e3..e02533611 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -140,11 +140,7 @@ export class TabsContainer this.preActionsContainer.className = 'dv-pre-actions-container'; this.tabs = new Tabs(group, accessor, { - showTabsOverflowControl: false, - }); - - this.tabs.onOverflowTabsChange((event) => { - this.toggleDropdown(event); + showTabsOverflowControl: !accessor.options.disableTabsOverflowList, }); this.voidContainer = new VoidContainer(this.accessor, this.group); @@ -156,6 +152,13 @@ export class TabsContainer this._element.appendChild(this.rightActionsContainer); this.addDisposables( + accessor.onDidOptionsChange(() => { + this.tabs.showTabsOverflowControl = + !accessor.options.disableTabsOverflowList; + }), + this.tabs.onOverflowTabsChange((event) => { + this.toggleDropdown(event); + }), this.tabs, this._onWillShowOverlay, this._onDrop, @@ -348,42 +351,40 @@ export class TabsContainer el.style.overflow = 'auto'; el.className = 'dv-tabs-overflow-container'; - this.tabs.tabs - .filter((tab) => this._overflowTabs.includes(tab.panel.id)) - .map((tab) => { - const panelObject = this.group.panels.find( - (panel) => panel === tab.panel - )!; + for (const tab of this.tabs.tabs.filter((tab) => + this._overflowTabs.includes(tab.panel.id) + )) { + const panelObject = this.group.panels.find( + (panel) => panel === tab.panel + )!; - const tabComponent = - panelObject.view.createTabRenderer( - 'headerOverflow' - ); + const tabComponent = + panelObject.view.createTabRenderer('headerOverflow'); - const child = tabComponent.element; + const child = tabComponent.element; - const wrapper = document.createElement('div'); - toggleClass(wrapper, 'dv-tab', true); - toggleClass( - wrapper, - 'dv-active-tab', - panelObject.api.isActive - ); - toggleClass( - wrapper, - 'dv-inactive-tab', - !panelObject.api.isActive - ); + const wrapper = document.createElement('div'); + toggleClass(wrapper, 'dv-tab', true); + toggleClass( + wrapper, + 'dv-active-tab', + panelObject.api.isActive + ); + toggleClass( + wrapper, + 'dv-inactive-tab', + !panelObject.api.isActive + ); - wrapper.addEventListener('mousedown', () => { - this.accessor.popupService.close(); - tab.element.scrollIntoView(); - tab.panel.api.setActive(); - }); - wrapper.appendChild(child); - - el.appendChild(wrapper); + wrapper.addEventListener('mousedown', () => { + this.accessor.popupService.close(); + tab.element.scrollIntoView(); + tab.panel.api.setActive(); }); + wrapper.appendChild(child); + + el.appendChild(wrapper); + } this.accessor.popupService.openPopover(el, { x: event.clientX, diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 348e7725c..e9abdf237 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -330,6 +330,9 @@ export class DockviewComponent readonly onDidAddGroup: Event = this._onDidAddGroup.event; + private readonly _onDidOptionsChange = new Emitter(); + readonly onDidOptionsChange: Event = this._onDidOptionsChange.event; + private readonly _onDidActiveGroupChange = new Emitter< DockviewGroupPanel | undefined >(); @@ -434,6 +437,7 @@ export class DockviewComponent this._onDidActiveGroupChange, this._onUnhandledDragOverEvent, this._onDidMaximizedGroupChange, + this._onDidOptionsChange, this.onDidViewVisibilityChangeMicroTaskQueue(() => { this.updateWatermark(); }), diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 4b096c941..bf0861006 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -72,6 +72,7 @@ export interface DockviewOptions { */ noPanelsOverlay?: 'emptyGroup' | 'watermark'; theme?: DockviewTheme; + disableTabsOverflowList?: boolean; } export interface DockviewDndOverlayEvent extends IAcceptableEvent { @@ -119,6 +120,7 @@ export const PROPERTY_KEYS_DOCKVIEW: (keyof DockviewOptions)[] = (() => { dndEdges: undefined, theme: undefined, gap: undefined, + disableTabsOverflowList: undefined, }; return Object.keys(properties) as (keyof DockviewOptions)[]; diff --git a/packages/dockview-core/src/scrollbar.ts b/packages/dockview-core/src/scrollbar.ts index 535ebd8e2..75db4e54a 100644 --- a/packages/dockview-core/src/scrollbar.ts +++ b/packages/dockview-core/src/scrollbar.ts @@ -4,11 +4,11 @@ import { CompositeDisposable } from './lifecycle'; import { clamp } from './math'; export class Scrollbar extends CompositeDisposable { - private _element: HTMLElement; - private _horizontalScrollbar: HTMLElement; + private readonly _element: HTMLElement; + private readonly _horizontalScrollbar: HTMLElement; private _scrollLeft: number = 0; private _animationTimer: any; - static MouseWheelSpeed = 1; + public static MouseWheelSpeed = 1; get element(): HTMLElement { return this._element; From c0119d65c0d7aedbe8a3b98296160ec75089d476 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 10 Mar 2025 21:17:21 +0000 Subject: [PATCH 76/83] chore: fixup docs --- .../dockview-core/src/api/component.api.ts | 13 ----- .../dockview/components/titlebar/tabs.scss | 29 ++++------ .../src/dockview/dockviewComponent.ts | 18 ------ .../dockview-core/src/dockview/options.ts | 5 -- packages/dockview-core/src/theme.scss | 56 +++++++++++-------- packages/docs/docs/core/panels/add.mdx | 4 ++ .../docs/docs/overview/getStarted/theme.mdx | 16 +++++- .../demo-dockview/src/gridActions.tsx | 47 +--------------- .../src/components/ui/reference/docRef.tsx | 2 + packages/docs/src/config/theme.config.ts | 16 +++--- 10 files changed, 71 insertions(+), 135 deletions(-) diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 0b0850ed0..7f3c40299 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -629,19 +629,6 @@ export class DockviewApi implements CommonApi { return this.component.totalPanels; } - /** - * @deprecated dockview: dockviewComponent.gap has been deprecated. Use `theme` instead. This will be removed in a future version. - */ - get gap(): number { - return this.component.gap; - } - - /** - * @deprecated dockview: dockviewComponent.setGap has been deprecated. Use `theme` instead. This will be removed in a future version. - */ - setGap(gap: number | undefined): void { - this.component.updateOptions({ gap: gap }); - } /** * Invoked when the active group changes. May be undefined if no group is active. diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss index 279659fac..df38c6741 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss @@ -43,17 +43,17 @@ &::-webkit-scrollbar-thumb { background: var(--dv-tabs-container-scrollbar-color); } +} - .dv-tab { - -webkit-user-drag: element; - outline: none; - padding: 0.25rem 0.5rem; - cursor: pointer; - position: relative; - box-sizing: border-box; - font-size: var(--dv-tab-font-size); - margin: var(--dv-tab-margin); - } +.dv-tab { + -webkit-user-drag: element; + outline: none; + padding: 0.25rem 0.5rem; + cursor: pointer; + position: relative; + box-sizing: border-box; + font-size: var(--dv-tab-font-size); + margin: var(--dv-tab-margin); } .dv-tabs-overflow-container { @@ -63,15 +63,6 @@ background-color: var(--dv-group-view-background-color); .dv-tab { - -webkit-user-drag: element; - outline: none; - padding: 0.25rem 0.5rem; - cursor: pointer; - position: relative; - box-sizing: border-box; - font-size: var(--dv-tab-font-size); - margin: var(--dv-tab-margin); - &:not(:last-child) { border-bottom: 1px solid var(--dv-tab-divider-color); } diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index e9abdf237..13bcb0ec2 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -195,10 +195,6 @@ export interface IDockviewComponent extends IBaseGrid { readonly totalPanels: number; readonly panels: IDockviewPanel[]; readonly orientation: Orientation; - /** - * @deprecated use `theme` instead. This will be removed in a future version - */ - readonly gap: number; readonly onDidDrop: Event; readonly onWillDrop: Event; readonly onWillShowOverlay: Event; @@ -373,13 +369,6 @@ export class DockviewComponent return this._api; } - get gap(): number { - console.warn( - 'dockview: dockviewComponent.gap has been deprecated. Use `theme` instead. This will be removed in a future version.' - ); - return this.gridview.margin; - } - get floatingGroups(): DockviewFloatingGroupPanel[] { return this._floatingGroups; } @@ -1180,13 +1169,6 @@ export class DockviewComponent override updateOptions(options: Partial): void { super.updateOptions(options); - if ('gap' in options) { - console.warn( - 'dockview: dockviewComponent.setGap has been deprecated. Use `theme` instead. This will be removed in a future version.' - ); - this.gridview.margin = options.gap ?? 0; - } - if ('floatingGroupBounds' in options) { for (const group of this._floatingGroups) { switch (options.floatingGroupBounds) { diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index bf0861006..86546934e 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -52,10 +52,6 @@ export interface DockviewOptions { }; popoutUrl?: string; defaultRenderer?: DockviewPanelRenderer; - /** - * @deprecated dockview: dockviewComponent.gap has been deprecated. Use `theme` instead. This will be removed in a future version. - */ - gap?: number; debug?: boolean; // #start dnd dndEdges?: false | DroptargetOverlayModel; @@ -119,7 +115,6 @@ export const PROPERTY_KEYS_DOCKVIEW: (keyof DockviewOptions)[] = (() => { noPanelsOverlay: undefined, dndEdges: undefined, theme: undefined, - gap: undefined, disableTabsOverflowList: undefined, }; diff --git a/packages/dockview-core/src/theme.scss b/packages/dockview-core/src/theme.scss index 0c98f152d..3b69d4c37 100644 --- a/packages/dockview-core/src/theme.scss +++ b/packages/dockview-core/src/theme.scss @@ -69,6 +69,8 @@ // --dv-separator-border: rgba(128, 128, 128, 0.35); --dv-paneview-header-border-color: rgb(51, 51, 51); + + --dv-scrollbar-background-color: rgba(0, 0, 0, 0.25); } .dockview-theme-dark { @@ -214,19 +216,21 @@ .dv-groupview { &.dv-active-group { > .dv-tabs-and-actions-container { - > .dv-tabs-container { - > .dv-tab.dv-active-tab { - position: relative; + > .dv-scrollable { + > .dv-tabs-container { + > .dv-tab.dv-active-tab { + position: relative; - &::after { - position: absolute; - left: 0px; - top: 0px; - content: ''; - width: 100%; - height: 1px; - background-color: #94527e; - z-index: 999; + &::after { + position: absolute; + left: 0px; + top: 0px; + content: ''; + width: 100%; + height: 1px; + background-color: #94527e; + z-index: 999; + } } } } @@ -234,19 +238,21 @@ } &.dv-inactive-group { > .dv-tabs-and-actions-container { - > .dv-tabs-container { - > .dv-tab.dv-active-tab { - position: relative; + > .dv-scrollable { + > .dv-tabs-container { + > .dv-tab.dv-active-tab { + position: relative; - &::after { - position: absolute; - left: 0px; - bottom: 0px; - content: ''; - width: 100%; - height: 1px; - background-color: #5e3d5a; - z-index: 999; + &::after { + position: absolute; + left: 0px; + bottom: 0px; + content: ''; + width: 100%; + height: 1px; + background-color: #5e3d5a; + z-index: 999; + } } } } @@ -444,6 +450,8 @@ padding: 10px; background-color: #f6f5f9; + --dv-scrollbar-background-color: rgba(0, 0, 0, 0.25); + .dv-resize-container { .dv-groupview { border: 2px solid rgb(255, 255, 255, 0.1); diff --git a/packages/docs/docs/core/panels/add.mdx b/packages/docs/docs/core/panels/add.mdx index cb151eb6e..aceba5e9c 100644 --- a/packages/docs/docs/core/panels/add.mdx +++ b/packages/docs/docs/core/panels/add.mdx @@ -13,6 +13,8 @@ Panels can be added through the dock api. + + ## Opening a Basic Panel To open a panel requires a unique `id` and the name of the `component` to render. @@ -95,6 +97,8 @@ See [Panel Rendering](/docs/core/panels/rendering). You can position a panel relative to an existing panel, group using `direction`. If you do not provide a reference panel or group then the panel will be positioned to the edge of the dock in the specified direction. + + #### Relative to another Panel ```ts diff --git a/packages/docs/docs/overview/getStarted/theme.mdx b/packages/docs/docs/overview/getStarted/theme.mdx index 6240340b9..131950277 100644 --- a/packages/docs/docs/overview/getStarted/theme.mdx +++ b/packages/docs/docs/overview/getStarted/theme.mdx @@ -34,8 +34,20 @@ Firstly, you should import `dockview.css`: ## Provided themes -`dockview` comes with a number of themes which are all CSS classes and can be found [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss). -To use a `dockview` theme the CSS must encapsulate the component. The current list of themes is: +`dockview` comes with a number of built-in themes. Each theme is represented as an object that can be imported. + +For dock components you should pass the theme object to the `theme` property, for other components such as split, pane and grid views you should +use set the themes associated CSS class to the `className` property. + +```tsx +import { themeAbyss } from "dockview"; + +// For dock components +theme={themeAbyss} + +// For other components +const {className} = themeAbyss; +``` diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx index ec16135ed..5c13128f5 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx @@ -2,7 +2,7 @@ import { DockviewApi } from 'dockview'; import * as React from 'react'; import { defaultConfig, nextId } from './defaultLayout'; -import { createRoot, Root } from 'react-dom/client'; +import { createRoot } from 'react-dom/client'; import { PanelBuilder } from './panelBuilder'; let mount = document.querySelector('.popover-anchor') as HTMLElement | null; @@ -151,21 +151,6 @@ export const GridActions = (props: { props.api?.addGroup(); }; - // const [gap, setGap] = React.useState(undefined); - - const [overlayMode, setOverlayMode] = React.useState(false); - - // React.useEffect(() => { - // if (!props.api) { - // return; - // } - // if (typeof gap === 'number') { - // props.api.setGap(gap); - // } else { - // setGap(props.api.gap); - // } - // }, [gap, props.api]); - return (
@@ -200,23 +185,6 @@ export const GridActions = (props: { Use Custom Watermark - {/* - - */} @@ -230,19 +198,6 @@ export const GridActions = (props: { Reset - {/*
- Grid Gap - setGap(Number(event.target.value))} - /> - -
*/}
); }; diff --git a/packages/docs/src/components/ui/reference/docRef.tsx b/packages/docs/src/components/ui/reference/docRef.tsx index 8319d1721..2fccfdd9e 100644 --- a/packages/docs/src/components/ui/reference/docRef.tsx +++ b/packages/docs/src/components/ui/reference/docRef.tsx @@ -192,6 +192,8 @@ function filter(docs: TypeSystem.Type, methods: string[]) { .map((v) => filter(v, methods)) .flat(); } + + return [docs]; } if (docs.kind === 'class' || docs.kind === 'interface') { diff --git a/packages/docs/src/config/theme.config.ts b/packages/docs/src/config/theme.config.ts index 0946720df..e509c2178 100644 --- a/packages/docs/src/config/theme.config.ts +++ b/packages/docs/src/config/theme.config.ts @@ -12,43 +12,43 @@ import { export const themeConfig = [ { id: themeDark, - key: '**[dockview-theme-dark](/demo?theme=dockview-theme-dark)**', + key: '**[Dark](/demo?theme=dark)**', text: '', }, { id: themeLight, - key: '**[dockview-theme-light](/demo?theme=dockview-theme-light)**', + key: '**[Light](/demo?theme=light)**', text: '', }, { id: themeVisualStudio, - key: '**[dockview-theme-vs](/demo?theme=dockview-theme-vs)**', + key: '**[Visual Studio](/demo?theme=visualStudio)**', text: 'Based on [Visual Studio](https://visualstudio.microsoft.com)', }, { id: themeAbyss, - key: '**[dockview-theme-abyss](/demo?theme=dockview-theme-abyss)**', + key: '**[Abyss](/demo?theme=abyss)**', text: 'Based on [Visual Studio Code](https://code.visualstudio.com/docs/getstarted/themes) abyss theme', }, { id: themeDracula, - key: '**[dockview-theme-dracula](/demo?theme=dockview-theme-dracula)**', + key: '**[Dracula](/demo?theme=dracula)**', text: 'Based on [Visual Studio Code](https://code.visualstudio.com/docs/getstarted/themes) dracula theme', }, { id: themeReplit, - key: '**[dockview-theme-replit](/demo?theme=dockview-theme-replit)**', + key: '**[Replit](/demo?theme=replit)**', text: 'Based on [Replit](https://replit.com)', }, { id: themeLightSpaced, - key: '**[dockview-theme-replit](/demo?theme=dockview-theme-kraken)**', + key: '**[Light Spaced](/demo?theme=lightSpaced)**', text: '', }, { id: themeAbyssSpaced, - key: '**[dockview-theme-replit](/demo?theme=dockview-theme-kraken)**', + key: '**[Abyss Spaced](/demo?theme=abyssSpaced)**', text: '', }, ]; From e34fb439130f89c6c43f618dce1fc95ad3aee0bb Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 10 Mar 2025 21:25:11 +0000 Subject: [PATCH 77/83] chore: theme docs --- .../docs/docs/overview/getStarted/theme.mdx | 9 +- packages/docs/src/generated/api.output.json | 961 +++++++++++++----- 2 files changed, 742 insertions(+), 228 deletions(-) diff --git a/packages/docs/docs/overview/getStarted/theme.mdx b/packages/docs/docs/overview/getStarted/theme.mdx index 131950277..dc025b57c 100644 --- a/packages/docs/docs/overview/getStarted/theme.mdx +++ b/packages/docs/docs/overview/getStarted/theme.mdx @@ -6,7 +6,7 @@ title: Theme import { CSSVariablesTable, ThemeTable } from '@site/src/components/cssVariables'; - +import { DocRef } from '@site/src/components/ui/reference/docRef'; Dockview components accept a `theme` property which is highly customizable, the theme is largly controlled through CSS however some properties can only be adjusted by direct editing variables of the `theme` object. @@ -55,6 +55,13 @@ const {className} = themeAbyss; The source code for all themes can be found [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss) and the associated CSS [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss). ::: +## Build your own theme + +You can define your own `DockviewTheme` object and pass it to the `theme` property. + + + + ## Customizing Theme The provided themes are controlled primarily through a long list of CSS variables which can be modified by the user either entirely for a new theme diff --git a/packages/docs/src/generated/api.output.json b/packages/docs/src/generated/api.output.json index d7e7ff7fd..40b17e55f 100644 --- a/packages/docs/src/generated/api.output.json +++ b/packages/docs/src/generated/api.output.json @@ -1831,20 +1831,6 @@ ] } }, - { - "name": "gap", - "code": "number", - "kind": "accessor", - "value": { - "name": "gap", - "code": "number", - "kind": "getSignature", - "returnType": { - "type": "intrinsic", - "value": "number" - } - } - }, { "name": "groups", "code": "DockviewGroupPanel[]", @@ -2509,7 +2495,7 @@ "summary": [ { "kind": "text", - "text": "Invoked before a group is dragged.\n\nCalling " + "text": "Invoked before a group is dragged.\r\n\r\nCalling " }, { "kind": "code", @@ -2540,7 +2526,7 @@ "summary": [ { "kind": "text", - "text": "Invoked before a group is dragged.\n\nCalling " + "text": "Invoked before a group is dragged.\r\n\r\nCalling " }, { "kind": "code", @@ -2563,7 +2549,7 @@ "summary": [ { "kind": "text", - "text": "Invoked before a panel is dragged.\n\nCalling " + "text": "Invoked before a panel is dragged.\r\n\r\nCalling " }, { "kind": "code", @@ -2594,7 +2580,7 @@ "summary": [ { "kind": "text", - "text": "Invoked before a panel is dragged.\n\nCalling " + "text": "Invoked before a panel is dragged.\r\n\r\nCalling " }, { "kind": "code", @@ -2617,7 +2603,7 @@ "summary": [ { "kind": "text", - "text": "Invoked when a Drag'n'Drop event occurs but before dockview handles it giving the user an opportunity to intecept and\nprevent the event from occuring using the standard " + "text": "Invoked when a Drag'n'Drop event occurs but before dockview handles it giving the user an opportunity to intecept and\r\nprevent the event from occuring using the standard " }, { "kind": "code", @@ -2625,7 +2611,7 @@ }, { "kind": "text", - "text": " syntax.\n\nPreventing certain events may causes unexpected behaviours, use carefully." + "text": " syntax.\r\n\r\nPreventing certain events may causes unexpected behaviours, use carefully." } ] }, @@ -2648,7 +2634,7 @@ "summary": [ { "kind": "text", - "text": "Invoked when a Drag'n'Drop event occurs but before dockview handles it giving the user an opportunity to intecept and\nprevent the event from occuring using the standard " + "text": "Invoked when a Drag'n'Drop event occurs but before dockview handles it giving the user an opportunity to intecept and\r\nprevent the event from occuring using the standard " }, { "kind": "code", @@ -2656,7 +2642,7 @@ }, { "kind": "text", - "text": " syntax.\n\nPreventing certain events may causes unexpected behaviours, use carefully." + "text": " syntax.\r\n\r\nPreventing certain events may causes unexpected behaviours, use carefully." } ] } @@ -2671,7 +2657,7 @@ "summary": [ { "kind": "text", - "text": "Invoked before an overlay is shown indicating a drop target.\n\nCalling " + "text": "Invoked before an overlay is shown indicating a drop target.\r\n\r\nCalling " }, { "kind": "code", @@ -2679,7 +2665,7 @@ }, { "kind": "text", - "text": " will prevent the overlay being shown and prevent\nthe any subsequent drop event." + "text": " will prevent the overlay being shown and prevent\r\nthe any subsequent drop event." } ] }, @@ -2702,7 +2688,7 @@ "summary": [ { "kind": "text", - "text": "Invoked before an overlay is shown indicating a drop target.\n\nCalling " + "text": "Invoked before an overlay is shown indicating a drop target.\r\n\r\nCalling " }, { "kind": "code", @@ -2710,7 +2696,7 @@ }, { "kind": "text", - "text": " will prevent the overlay being shown and prevent\nthe any subsequent drop event." + "text": " will prevent the overlay being shown and prevent\r\nthe any subsequent drop event." } ] } @@ -2906,7 +2892,7 @@ }, { "name": "addGroup", - "code": "(options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition): DockviewGroupPanel", + "code": "(options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup): DockviewGroupPanel", "kind": "method", "signature": [ { @@ -2923,10 +2909,25 @@ "parameters": [ { "name": "options", - "code": "options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition", + "code": "options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup", "type": { "type": "or", "values": [ + { + "type": "intersection", + "values": [ + { + "type": "reference", + "value": "GroupOptions", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "AbsolutePosition", + "source": "dockview-core" + } + ] + }, { "type": "intersection", "values": [ @@ -2956,21 +2957,6 @@ "source": "dockview-core" } ] - }, - { - "type": "intersection", - "values": [ - { - "type": "reference", - "value": "GroupOptions", - "source": "dockview-core" - }, - { - "type": "reference", - "value": "AbsolutePosition", - "source": "dockview-core" - } - ] } ] }, @@ -2982,7 +2968,7 @@ "value": "DockviewGroupPanel", "source": "dockview-core" }, - "code": "(options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition): DockviewGroupPanel", + "code": "(options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup): DockviewGroupPanel", "kind": "callSignature" } ], @@ -3936,43 +3922,6 @@ ] } }, - { - "name": "setGap", - "code": "(gap: number | undefined): void", - "kind": "method", - "signature": [ - { - "name": "setGap", - "typeParameters": [], - "parameters": [ - { - "name": "gap", - "code": "gap: number | undefined", - "type": { - "type": "or", - "values": [ - { - "type": "intrinsic", - "value": "number" - }, - { - "type": "intrinsic", - "value": "undefined" - } - ] - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "void" - }, - "code": "(gap: number | undefined): void", - "kind": "callSignature" - } - ] - }, { "name": "toJSON", "code": "(): SerializedDockview", @@ -4431,6 +4380,25 @@ "isReadonly": true } }, + { + "name": "onDidOptionsChange", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "intrinsic", + "value": "void" + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "onDidRemove", "code": "Event", @@ -4623,6 +4591,32 @@ "isReadonly": true } }, + { + "name": "popupService", + "code": "PopupService", + "kind": "property", + "type": { + "type": "reference", + "value": "PopupService", + "source": "dockview-core" + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "rootDropTargetContainer", + "code": "DropTargetAnchorContainer", + "kind": "property", + "type": { + "type": "reference", + "value": "DropTargetAnchorContainer", + "source": "dockview-core" + }, + "flags": { + "isReadonly": true + } + }, { "name": "activeGroup", "code": "BaseGrid.T | undefined", @@ -4734,20 +4728,6 @@ } } }, - { - "name": "gap", - "code": "number", - "kind": "accessor", - "value": { - "name": "gap", - "code": "number", - "kind": "getSignature", - "returnType": { - "type": "intrinsic", - "value": "number" - } - } - }, { "name": "groups", "code": "BaseGrid.T[]", @@ -5067,7 +5047,7 @@ }, { "name": "addGroup", - "code": "(options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition): DockviewGroupPanel", + "code": "(options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup): DockviewGroupPanel", "kind": "method", "signature": [ { @@ -5076,10 +5056,25 @@ "parameters": [ { "name": "options", - "code": "options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition", + "code": "options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup", "type": { "type": "or", "values": [ + { + "type": "intersection", + "values": [ + { + "type": "reference", + "value": "GroupOptions", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "AbsolutePosition", + "source": "dockview-core" + } + ] + }, { "type": "intersection", "values": [ @@ -5109,21 +5104,6 @@ "source": "dockview-core" } ] - }, - { - "type": "intersection", - "values": [ - { - "type": "reference", - "value": "GroupOptions", - "source": "dockview-core" - }, - { - "type": "reference", - "value": "AbsolutePosition", - "source": "dockview-core" - } - ] } ] }, @@ -5135,7 +5115,7 @@ "value": "DockviewGroupPanel", "source": "dockview-core" }, - "code": "(options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition): DockviewGroupPanel", + "code": "(options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup): DockviewGroupPanel", "kind": "callSignature" } ] @@ -7858,6 +7838,30 @@ } } }, + { + "name": "dropTargetContainer", + "code": "DropTargetAnchorContainer | 'null'", + "kind": "accessor", + "value": { + "name": "dropTargetContainer", + "code": "DropTargetAnchorContainer | 'null'", + "kind": "getSignature", + "returnType": { + "type": "or", + "values": [ + { + "type": "reference", + "value": "DropTargetAnchorContainer", + "source": "dockview-core" + }, + { + "type": "literal", + "value": null + } + ] + } + } + }, { "name": "element", "code": "HTMLElement", @@ -9893,7 +9897,7 @@ }, { "name": "onDidDrop", - "code": "Event", + "code": "Event", "kind": "property", "type": { "type": "reference", @@ -9902,7 +9906,7 @@ "typeArguments": [ { "type": "reference", - "value": "PaneviewDropEvent", + "value": "PaneviewDidDropEvent", "source": "dockview-core" } ] @@ -15119,7 +15123,7 @@ }, { "name": "onDidDrop", - "code": "Event", + "code": "Event", "kind": "property", "type": { "type": "reference", @@ -15128,7 +15132,7 @@ "typeArguments": [ { "type": "reference", - "value": "PaneviewDropEvent", + "value": "PaneviewDidDropEvent", "source": "dockview-core" } ] @@ -16462,7 +16466,7 @@ }, { "name": "onDidDrop", - "code": "Event", + "code": "Event", "kind": "accessor", "value": { "name": "onDidDrop", @@ -16474,7 +16478,7 @@ } ] }, - "code": "Event", + "code": "Event", "kind": "getSignature", "returnType": { "type": "reference", @@ -16483,7 +16487,7 @@ "typeArguments": [ { "type": "reference", - "value": "PaneviewDropEvent", + "value": "PaneviewDidDropEvent", "source": "dockview-core" } ] @@ -17280,7 +17284,7 @@ }, { "name": "onDidDrop", - "code": "Event", + "code": "Event", "kind": "property", "type": { "type": "reference", @@ -17289,7 +17293,7 @@ "typeArguments": [ { "type": "reference", - "value": "PaneviewDropEvent", + "value": "PaneviewDidDropEvent", "source": "dockview-core" } ] @@ -18656,6 +18660,130 @@ "BasePanelView" ] }, + "PaneviewUnhandledDragOverEvent": { + "kind": "class", + "name": "PaneviewUnhandledDragOverEvent", + "children": [ + { + "name": "constructor", + "kind": "constructor", + "code": "" + }, + { + "name": "getData", + "code": "(): PaneTransfer | undefined", + "kind": "property", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "(): PaneTransfer | undefined", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [], + "returnType": { + "type": "or", + "values": [ + { + "type": "reference", + "value": "PaneTransfer", + "source": "dockview-core" + }, + { + "type": "intrinsic", + "value": "undefined" + } + ] + }, + "code": "(): PaneTransfer | undefined", + "kind": "callSignature" + } + ] + } + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "nativeEvent", + "code": "DragEvent", + "kind": "property", + "type": { + "type": "reference", + "value": "DragEvent", + "source": "typescript" + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "panel", + "code": "IPaneviewPanel", + "kind": "property", + "type": { + "type": "reference", + "value": "IPaneviewPanel", + "source": "dockview-core" + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "position", + "code": "Position", + "kind": "property", + "type": { + "type": "reference", + "value": "Position", + "source": "dockview-core" + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "isAccepted", + "code": "boolean", + "kind": "accessor", + "value": { + "name": "isAccepted", + "code": "boolean", + "kind": "getSignature", + "returnType": { + "type": "intrinsic", + "value": "boolean" + } + } + }, + { + "name": "accept", + "code": "(): void", + "kind": "method", + "signature": [ + { + "name": "accept", + "typeParameters": [], + "parameters": [], + "returnType": { + "type": "intrinsic", + "value": "void" + }, + "code": "(): void", + "kind": "callSignature" + } + ] + } + ], + "extends": [ + "AcceptableEvent" + ] + }, "Splitview": { "kind": "class", "name": "Splitview", @@ -19571,7 +19699,7 @@ "summary": [ { "kind": "text", - "text": "Invoked whenever any aspect of the layout changes.\nIf listening to this event it may be worth debouncing ouputs." + "text": "Invoked whenever any aspect of the layout changes.\r\nIf listening to this event it may be worth debouncing ouputs." } ] }, @@ -19593,7 +19721,7 @@ "summary": [ { "kind": "text", - "text": "Invoked whenever any aspect of the layout changes.\nIf listening to this event it may be worth debouncing ouputs." + "text": "Invoked whenever any aspect of the layout changes.\r\nIf listening to this event it may be worth debouncing ouputs." } ] } @@ -20189,7 +20317,7 @@ }, { "kind": "text", - "text": " method\nfor the subsequent resize." + "text": " method\r\nfor the subsequent resize." } ] }, @@ -20251,7 +20379,7 @@ }, { "kind": "text", - "text": " method\nfor the subsequent resize." + "text": " method\r\nfor the subsequent resize." } ] } @@ -21730,26 +21858,6 @@ "kind": "constructor", "code": "" }, - { - "name": "onChanged", - "code": "Event", - "kind": "property", - "type": { - "type": "reference", - "value": "Event", - "source": "dockview-core", - "typeArguments": [ - { - "type": "reference", - "value": "MouseEvent", - "source": "typescript" - } - ] - }, - "flags": { - "isReadonly": true - } - }, { "name": "onDragStart", "code": "Event", @@ -21790,6 +21898,26 @@ "isReadonly": true } }, + { + "name": "onPointerDown", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "MouseEvent", + "source": "typescript" + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "onWillShowOverlay", "code": "Event", @@ -24376,7 +24504,7 @@ }, { "kind": "text", - "text": ".\nCall " + "text": ".\r\nCall " }, { "kind": "code", @@ -24413,6 +24541,40 @@ "isOptional": true } }, + { + "name": "disableTabsOverflowList", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "dndEdges", + "code": "DroptargetOverlayModel | 'false'", + "kind": "property", + "type": { + "type": "or", + "values": [ + { + "type": "reference", + "value": "DroptargetOverlayModel", + "source": "dockview-core" + }, + { + "type": "literal", + "value": false + } + ] + }, + "flags": { + "isOptional": true + } + }, { "name": "floatingGroupBounds", "code": "{ minimumHeightWithinViewport?: number, minimumWidthWithinViewport?: number } | 'boundedWithinViewport'", @@ -24464,26 +24626,6 @@ "isOptional": true } }, - { - "name": "gap", - "code": "number", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "number" - }, - "flags": { - "isOptional": true - }, - "comment": { - "summary": [ - { - "kind": "text", - "text": "Pixel gap between groups" - } - ] - } - }, { "name": "hideBorders", "code": "boolean", @@ -24568,6 +24710,28 @@ }, "flags": { "isOptional": true + }, + "comment": { + "summary": [], + "blockTags": [ + { + "tag": "@deprecated", + "content": [ + { + "kind": "text", + "text": "use " + }, + { + "kind": "code", + "text": "`dndEdges`" + }, + { + "kind": "text", + "text": " instead. To be removed in a future version." + } + ] + } + ] } }, { @@ -24590,6 +24754,19 @@ "flags": { "isOptional": true } + }, + { + "name": "theme", + "code": "DockviewTheme", + "kind": "property", + "type": { + "type": "reference", + "value": "DockviewTheme", + "source": "dockview-core" + }, + "flags": { + "isOptional": true + } } ], "extends": [] @@ -25580,6 +25757,78 @@ ], "extends": [] }, + "DockviewTheme": { + "kind": "interface", + "name": "DockviewTheme", + "children": [ + { + "name": "className", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": {} + }, + { + "name": "dndOverlayMounting", + "code": "'relative' | 'absolute'", + "kind": "property", + "type": { + "type": "or", + "values": [ + { + "type": "literal", + "value": "relative" + }, + { + "type": "literal", + "value": "absolute" + } + ] + }, + "flags": { + "isOptional": true + } + }, + { + "name": "gap", + "code": "number", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "number" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "includeHeaderWhenHoverOverContent", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "name", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": {} + } + ], + "extends": [] + }, "ExpansionEvent": { "kind": "interface", "name": "ExpansionEvent", @@ -28587,18 +28836,6 @@ "isReadonly": true } }, - { - "name": "gap", - "code": "number", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "number" - }, - "flags": { - "isReadonly": true - } - }, { "name": "getGroupPanel", "code": "(id: string): IDockviewPanel | undefined", @@ -29264,7 +29501,7 @@ }, { "name": "addGroup", - "code": "(options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition): DockviewGroupPanel", + "code": "(options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup): DockviewGroupPanel", "kind": "method", "signature": [ { @@ -29273,10 +29510,25 @@ "parameters": [ { "name": "options", - "code": "options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition", + "code": "options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup", "type": { "type": "or", "values": [ + { + "type": "intersection", + "values": [ + { + "type": "reference", + "value": "GroupOptions", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "AbsolutePosition", + "source": "dockview-core" + } + ] + }, { "type": "intersection", "values": [ @@ -29306,21 +29558,6 @@ "source": "dockview-core" } ] - }, - { - "type": "intersection", - "values": [ - { - "type": "reference", - "value": "GroupOptions", - "source": "dockview-core" - }, - { - "type": "reference", - "value": "AbsolutePosition", - "source": "dockview-core" - } - ] } ] }, @@ -29332,7 +29569,7 @@ "value": "DockviewGroupPanel", "source": "dockview-core" }, - "code": "(options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition): DockviewGroupPanel", + "code": "(options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup): DockviewGroupPanel", "kind": "callSignature" } ] @@ -35173,7 +35410,7 @@ }, { "name": "onDidDrop", - "code": "Event", + "code": "Event", "kind": "property", "type": { "type": "reference", @@ -35182,7 +35419,7 @@ "typeArguments": [ { "type": "reference", - "value": "PaneviewDropEvent", + "value": "PaneviewDidDropEvent", "source": "dockview-core" } ] @@ -36964,7 +37201,7 @@ }, { "name": "init", - "code": "(parameters: GroupPanelPartInitParameters): void", + "code": "(parameters: TabPartInitParameters): void", "kind": "method", "signature": [ { @@ -36973,10 +37210,10 @@ "parameters": [ { "name": "parameters", - "code": "parameters: GroupPanelPartInitParameters", + "code": "parameters: TabPartInitParameters", "type": { "type": "reference", - "value": "GroupPanelPartInitParameters", + "value": "TabPartInitParameters", "source": "dockview-core" }, "kind": "parameter" @@ -36986,7 +37223,7 @@ "type": "intrinsic", "value": "void" }, - "code": "(parameters: GroupPanelPartInitParameters): void", + "code": "(parameters: TabPartInitParameters): void", "kind": "callSignature" } ] @@ -38758,6 +38995,115 @@ "PanelInitParameters" ] }, + "PaneviewDndOverlayEvent": { + "kind": "interface", + "name": "PaneviewDndOverlayEvent", + "children": [ + { + "name": "getData", + "code": "(): PaneTransfer | undefined", + "kind": "property", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "(): PaneTransfer | undefined", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [], + "returnType": { + "type": "or", + "values": [ + { + "type": "reference", + "value": "PaneTransfer", + "source": "dockview-core" + }, + { + "type": "intrinsic", + "value": "undefined" + } + ] + }, + "code": "(): PaneTransfer | undefined", + "kind": "callSignature" + } + ] + } + }, + "flags": {} + }, + { + "name": "isAccepted", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "nativeEvent", + "code": "DragEvent", + "kind": "property", + "type": { + "type": "reference", + "value": "DragEvent", + "source": "typescript" + }, + "flags": {} + }, + { + "name": "panel", + "code": "IPaneviewPanel", + "kind": "property", + "type": { + "type": "reference", + "value": "IPaneviewPanel", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "position", + "code": "Position", + "kind": "property", + "type": { + "type": "reference", + "value": "Position", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "accept", + "code": "(): void", + "kind": "method", + "signature": [ + { + "name": "accept", + "typeParameters": [], + "parameters": [], + "returnType": { + "type": "intrinsic", + "value": "void" + }, + "code": "(): void", + "kind": "callSignature" + } + ] + } + ], + "extends": [ + "IAcceptableEvent" + ] + }, "PaneviewDropEvent": { "kind": "interface", "name": "PaneviewDropEvent", @@ -40466,14 +40812,26 @@ }, { "name": "orientation", - "code": "Orientation", + "code": "Orientation.VERTICAL | Orientation.HORIZONTAL", "kind": "property", "type": { - "type": "reference", - "value": "Orientation", - "source": "dockview-core" + "type": "or", + "values": [ + { + "type": "reference", + "value": "Orientation.VERTICAL", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "Orientation.HORIZONTAL", + "source": "dockview-core" + } + ] }, - "flags": {} + "flags": { + "isOptional": true + } }, { "name": "proportionalLayout", @@ -40604,14 +40962,26 @@ }, { "name": "orientation", - "code": "Orientation", + "code": "Orientation.VERTICAL | Orientation.HORIZONTAL", "kind": "property", "type": { - "type": "reference", - "value": "Orientation", - "source": "dockview-core" + "type": "or", + "values": [ + { + "type": "reference", + "value": "Orientation.VERTICAL", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "Orientation.HORIZONTAL", + "source": "dockview-core" + } + ] }, - "flags": {} + "flags": { + "isOptional": true + } }, { "name": "proportionalLayout", @@ -41168,6 +41538,69 @@ ], "extends": [] }, + "TabPartInitParameters": { + "kind": "interface", + "name": "TabPartInitParameters", + "children": [ + { + "name": "api", + "code": "DockviewPanelApi", + "kind": "property", + "type": { + "type": "reference", + "value": "DockviewPanelApi", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "containerApi", + "code": "DockviewApi", + "kind": "property", + "type": { + "type": "reference", + "value": "DockviewApi", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "params", + "code": "Parameters", + "kind": "property", + "type": { + "type": "reference", + "value": "Parameters", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "tabLocation", + "code": "TabLocation", + "kind": "property", + "type": { + "type": "reference", + "value": "TabLocation", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "title", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": {} + } + ], + "extends": [ + "GroupPanelPartInitParameters" + ] + }, "TitleEvent": { "kind": "interface", "name": "TitleEvent", @@ -41391,7 +41824,7 @@ "summary": [ { "kind": "text", - "text": "If true then add the panel without setting it as the active panel.\n\nDefaults to " + "text": "If true then add the panel without setting it as the active panel.\r\n\r\nDefaults to " }, { "kind": "code", @@ -41458,7 +41891,7 @@ "summary": [ { "kind": "text", - "text": "The rendering mode of the panel.\n\nThis dictates what happens to the HTML of the panel when it is hidden." + "text": "The rendering mode of the panel.\r\n\r\nThis dictates what happens to the HTML of the panel when it is hidden." } ] } @@ -41498,7 +41931,7 @@ "summary": [ { "kind": "text", - "text": "The title for the panel which can be accessed within both the tab and component.\n\nIf using the default tab renderer this title will be displayed in the tab." + "text": "The title for the panel which can be accessed within both the tab and component.\r\n\r\nIf using the default tab renderer this title will be displayed in the tab." } ] } @@ -41538,6 +41971,14 @@ }, "Direction": { "name": "Direction", + "comment": { + "summary": [ + { + "kind": "text", + "text": "A direction in which a panel can be moved or placed relative to another panel." + } + ] + }, "code": "'within' | 'below' | 'above' | 'right' | 'left'", "typeParameters": [], "type": { @@ -41914,18 +42355,44 @@ }, "IDockviewPanelHeaderProps": { "name": "IDockviewPanelHeaderProps", - "code": "IGroupPanelBaseProps", + "code": "{ tabLocation: TabLocation } & IGroupPanelBaseProps", "typeParameters": [], "type": { - "type": "reference", - "value": "IGroupPanelBaseProps", - "source": "dockview-core", - "typeArguments": [ + "type": "intersection", + "values": [ + { + "type": "reflection", + "value": { + "name": "__type", + "code": "{ tabLocation: TabLocation }", + "kind": "typeLiteral", + "properties": [ + { + "name": "tabLocation", + "code": "TabLocation", + "kind": "property", + "type": { + "type": "reference", + "value": "TabLocation", + "source": "dockview-core" + }, + "flags": {} + } + ] + } + }, { "type": "reference", - "value": "T", + "value": "IGroupPanelBaseProps", "source": "dockview-core", - "refersToTypeParameter": true + "typeArguments": [ + { + "type": "reference", + "value": "T", + "source": "dockview-core", + "refersToTypeParameter": true + } + ] } ] }, @@ -42181,6 +42648,46 @@ "code": "", "kind": "variable" }, + "themeAbyss": { + "name": "themeAbyss", + "code": "", + "kind": "variable" + }, + "themeAbyssSpaced": { + "name": "themeAbyssSpaced", + "code": "", + "kind": "variable" + }, + "themeDark": { + "name": "themeDark", + "code": "", + "kind": "variable" + }, + "themeDracula": { + "name": "themeDracula", + "code": "", + "kind": "variable" + }, + "themeLight": { + "name": "themeLight", + "code": "", + "kind": "variable" + }, + "themeLightSpaced": { + "name": "themeLightSpaced", + "code": "", + "kind": "variable" + }, + "themeReplit": { + "name": "themeReplit", + "code": "", + "kind": "variable" + }, + "themeVisualStudio": { + "name": "themeVisualStudio", + "code": "", + "kind": "variable" + }, "createDockview": { "name": "createDockview", "code": "(element: HTMLElement, options: DockviewComponentOptions): DockviewApi", @@ -43528,7 +44035,7 @@ }, { "name": "onDidDrop", - "code": "(event: PaneviewDropEvent): void", + "code": "(event: PaneviewDidDropEvent): void", "kind": "method", "signature": [ { @@ -43537,10 +44044,10 @@ "parameters": [ { "name": "event", - "code": "event: PaneviewDropEvent", + "code": "event: PaneviewDidDropEvent", "type": { "type": "reference", - "value": "PaneviewDropEvent", + "value": "PaneviewDidDropEvent", "source": "dockview-core" }, "kind": "parameter" @@ -43550,7 +44057,7 @@ "type": "intrinsic", "value": "void" }, - "code": "(event: PaneviewDropEvent): void", + "code": "(event: PaneviewDidDropEvent): void", "kind": "callSignature" } ] From 090f2d26f896f29c865ae0683b1e0320f9140688 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 10 Mar 2025 21:28:33 +0000 Subject: [PATCH 78/83] chore: docs --- packages/docs/docs/core/dnd/dragAndDrop.mdx | 4 ++-- .../docs/sandboxes/react/dockview/dnd-external/src/app.tsx | 2 +- .../docs/templates/dockview/dnd-external/react/src/app.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/docs/docs/core/dnd/dragAndDrop.mdx b/packages/docs/docs/core/dnd/dragAndDrop.mdx index 33293c4c2..bb68e83c2 100644 --- a/packages/docs/docs/core/dnd/dragAndDrop.mdx +++ b/packages/docs/docs/core/dnd/dragAndDrop.mdx @@ -24,12 +24,12 @@ The dock makes heavy use of drag and drop functionalities. # Drag And Drop -You can override the conditions of the far edge overlays through the `rootOverlayModel` prop. +You can override the conditions of the far edge overlays through the `dndEdges` prop. ```tsx { onReady={onReady} className={`${props.theme || 'dockview-theme-abyss'}`} onDidDrop={onDidDrop} - rootOverlayModel={{ + dndEdges={{ size: { value: 100, type: 'pixels' }, activationSize: { value: 5, type: 'percentage' }, }} diff --git a/packages/docs/templates/dockview/dnd-external/react/src/app.tsx b/packages/docs/templates/dockview/dnd-external/react/src/app.tsx index 19473533e..5aba3fcbc 100644 --- a/packages/docs/templates/dockview/dnd-external/react/src/app.tsx +++ b/packages/docs/templates/dockview/dnd-external/react/src/app.tsx @@ -179,7 +179,7 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { onReady={onReady} className={`${props.theme || 'dockview-theme-abyss'}`} onDidDrop={onDidDrop} - rootOverlayModel={{ + dndEdges={{ size: { value: 100, type: 'pixels' }, activationSize: { value: 5, type: 'percentage' }, }} From 4eff83e9a043b5572d1f88a80c4a5c711c2fe1b8 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 10 Mar 2025 21:35:17 +0000 Subject: [PATCH 79/83] bug: remove element after dispose --- .../gridview/gridviewComponent.spec.ts | 60 +++++++++---------- .../paneview/paneviewComponent.spec.ts | 1 + .../splitview/splitviewComponent.spec.ts | 1 + .../src/paneview/paneviewComponent.ts | 2 + .../src/splitview/splitviewComponent.ts | 2 + 5 files changed, 36 insertions(+), 30 deletions(-) diff --git a/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts b/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts index 5f9febc5c..3063c692d 100644 --- a/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts @@ -58,7 +58,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -84,7 +84,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -121,7 +121,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -188,7 +188,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -322,7 +322,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -365,7 +365,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -495,7 +495,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -524,14 +524,14 @@ describe('gridview', () => { gridview.dispose(); - expect(container.childNodes.length).toBe(0); + expect(container.children.length).toBe(0); }); test('#1/VERTICAL', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -596,7 +596,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -661,7 +661,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -744,7 +744,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -845,7 +845,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -946,7 +946,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -1047,7 +1047,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -1178,7 +1178,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -1309,7 +1309,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -1442,7 +1442,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -1573,7 +1573,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.HORIZONTAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -1704,7 +1704,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -1838,7 +1838,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -1875,7 +1875,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -1911,7 +1911,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -1956,7 +1956,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -2085,7 +2085,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -2218,7 +2218,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -2500,7 +2500,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -2864,7 +2864,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -2881,7 +2881,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); @@ -2899,7 +2899,7 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - createComponent: (options) => { + createComponent: (options) => { switch (options.name) { case 'default': return new TestGridview(options.id, options.name); diff --git a/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts b/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts index 8cfc7fc2e..a30f73fd5 100644 --- a/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts @@ -86,6 +86,7 @@ describe('componentPaneview', () => { paneview.dispose(); expect(container.parentElement).toBe(root); + expect(container.children.length).toBe(0); }); test('vertical panels', () => { diff --git a/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts b/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts index 90d0b3986..6c034091c 100644 --- a/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts @@ -46,6 +46,7 @@ describe('componentSplitview', () => { splitview.dispose(); expect(container.parentElement).toBe(root); + expect(container.children.length).toBe(0); }); test('event leakage', () => { diff --git a/packages/dockview-core/src/paneview/paneviewComponent.ts b/packages/dockview-core/src/paneview/paneviewComponent.ts index b47ca0181..4856fb1ad 100644 --- a/packages/dockview-core/src/paneview/paneviewComponent.ts +++ b/packages/dockview-core/src/paneview/paneviewComponent.ts @@ -476,6 +476,8 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { } this._viewDisposables.clear(); + this.element.remove(); + this.paneview.dispose(); } } diff --git a/packages/dockview-core/src/splitview/splitviewComponent.ts b/packages/dockview-core/src/splitview/splitviewComponent.ts index a165ba4b3..b97855f58 100644 --- a/packages/dockview-core/src/splitview/splitviewComponent.ts +++ b/packages/dockview-core/src/splitview/splitviewComponent.ts @@ -425,6 +425,8 @@ export class SplitviewComponent view.dispose(); } + this.element.remove(); + super.dispose(); } } From 1030bdb7787ac320cd23615cea793a6753b518b3 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:16:57 +0000 Subject: [PATCH 80/83] feat: rename theme property --- .../src/dockview/components/panel/content.ts | 2 +- packages/dockview-core/src/dockview/theme.ts | 22 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/dockview-core/src/dockview/components/panel/content.ts b/packages/dockview-core/src/dockview/components/panel/content.ts index 08703179d..6c37035b0 100644 --- a/packages/dockview-core/src/dockview/components/panel/content.ts +++ b/packages/dockview-core/src/dockview/components/panel/content.ts @@ -59,7 +59,7 @@ export class ContentContainer this.dropTarget = new Droptarget(this.element, { getOverlayOutline: () => { - return accessor.options.theme?.includeHeaderWhenHoverOverContent + return accessor.options.theme?.dndPanelOverlay === 'group' ? this.element.parentElement : null; }, diff --git a/packages/dockview-core/src/dockview/theme.ts b/packages/dockview-core/src/dockview/theme.ts index 4a921e2db..c010b84d0 100644 --- a/packages/dockview-core/src/dockview/theme.ts +++ b/packages/dockview-core/src/dockview/theme.ts @@ -1,9 +1,25 @@ export interface DockviewTheme { + /** + * The name of the theme + */ name: string; + /** + * The class name to apply to the theme containing the CSS variables settings. + */ className: string; + /** + * The gap between the groups + */ gap?: number; + /** + * The mouting position of the overlay shown when dragging a panel. `absolute` + * will mount the overlay to root of the dockview component whereas `relative` will mount the overlay to the group container. + */ dndOverlayMounting?: 'absolute' | 'relative'; - includeHeaderWhenHoverOverContent?: boolean; + /** + * When dragging a panel, the overlay can either encompass the panel contents or the entire group including the tab header space. + */ + dndPanelOverlay?: 'content' | 'group'; } export const themeDark: DockviewTheme = { @@ -42,7 +58,7 @@ export const themeAbyssSpaced: DockviewTheme = { className: 'dockview-theme-abyss-spaced', gap: 10, dndOverlayMounting: 'absolute', - includeHeaderWhenHoverOverContent: true, + dndPanelOverlay: 'group', }; export const themeLightSpaced: DockviewTheme = { @@ -50,5 +66,5 @@ export const themeLightSpaced: DockviewTheme = { className: 'dockview-theme-light-spaced', gap: 10, dndOverlayMounting: 'absolute', - includeHeaderWhenHoverOverContent: true, + dndPanelOverlay: 'group', }; From 21d94b654be702448450f9dba39d4faa6683aa0e Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:18:27 +0000 Subject: [PATCH 81/83] chore: generate docs --- packages/docs/src/generated/api.output.json | 91 ++++++++++++++++++--- 1 file changed, 78 insertions(+), 13 deletions(-) diff --git a/packages/docs/src/generated/api.output.json b/packages/docs/src/generated/api.output.json index 40b17e55f..f8a8a944a 100644 --- a/packages/docs/src/generated/api.output.json +++ b/packages/docs/src/generated/api.output.json @@ -25769,7 +25769,15 @@ "type": "intrinsic", "value": "string" }, - "flags": {} + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The class name to apply to the theme containing the CSS variables settings." + } + ] + } }, { "name": "dndOverlayMounting", @@ -25790,6 +25798,59 @@ }, "flags": { "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The mouting position of the overlay shown when dragging a panel. " + }, + { + "kind": "code", + "text": "`absolute`" + }, + { + "kind": "text", + "text": "\nwill mount the overlay to root of the dockview component whereas " + }, + { + "kind": "code", + "text": "`relative`" + }, + { + "kind": "text", + "text": " will mount the overlay to the group container." + } + ] + } + }, + { + "name": "dndPanelOverlay", + "code": "'content' | 'group'", + "kind": "property", + "type": { + "type": "or", + "values": [ + { + "type": "literal", + "value": "content" + }, + { + "type": "literal", + "value": "group" + } + ] + }, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "When dragging a panel, the overlay can either encompass the panel contents or the entire group including the tab header space." + } + ] } }, { @@ -25802,18 +25863,14 @@ }, "flags": { "isOptional": true - } - }, - { - "name": "includeHeaderWhenHoverOverContent", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" }, - "flags": { - "isOptional": true + "comment": { + "summary": [ + { + "kind": "text", + "text": "The gap between the groups" + } + ] } }, { @@ -25824,7 +25881,15 @@ "type": "intrinsic", "value": "string" }, - "flags": {} + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The name of the theme" + } + ] + } } ], "extends": [] From ac92d59d0adc852ca5c3d52a7fa8986b0dae7464 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:24:59 +0000 Subject: [PATCH 82/83] chore: v4.0.0 docs --- .../docs/blog/2025-03-12-dockview-4.0.0.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 packages/docs/blog/2025-03-12-dockview-4.0.0.md diff --git a/packages/docs/blog/2025-03-12-dockview-4.0.0.md b/packages/docs/blog/2025-03-12-dockview-4.0.0.md new file mode 100644 index 000000000..e7a759965 --- /dev/null +++ b/packages/docs/blog/2025-03-12-dockview-4.0.0.md @@ -0,0 +1,24 @@ +--- +slug: dockview-4.0.0-release +title: Dockview 4.0.0 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features +- To control the theme of dockview you should no longer pass a `dv-theme-*` class, instead directly use the `theme` property. See [Themes](https://dockview.dev/demo) for more details. [#850](https://github.com/mathuo/dockview/pull/850) +- Introduces a new dnd overlay model with improved animations and customization options allowing themes to take more fine grained control over the dnd overlay styles. [#850](https://github.com/mathuo/dockview/pull/850) +- Custom scrollbar on tab headers for better UX [#822](https://github.com/mathuo/dockview/pull/822) +- When tabs are hidden within scrollbar hidden tabs can be selected from a dropdown that appears in header [#822](https://github.com/mathuo/dockview/pull/822) + +## 🛠 Miscs + +- Bug: Remove elements from DOM after disposable for Splitview and Paneview components [#870](https://github.com/mathuo/dockview/pull/870) + +## 🔥 Breaking changes + +- `setGap(gap: number | undefined): void` and `gap(): number` have been removed. The gap property is now controlled directly within the chosen Theme. See [Themes](https://dockview.dev) for more details. +- `DockviewDefaultTab` requires a `tabLocation` field, to mimic existing behaviour use `tabLocation="header"`. From bffd2dea89fa216bb077a3c333308d0fc2482c4e Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:25:13 +0000 Subject: [PATCH 83/83] chore(release): publish v4.0.0 --- lerna.json | 2 +- packages/dockview-angular/package.json | 4 ++-- packages/dockview-core/package.json | 2 +- packages/dockview-react/package.json | 4 ++-- packages/dockview-vue/package.json | 4 ++-- packages/dockview/package.json | 4 ++-- packages/docs/package.json | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lerna.json b/lerna.json index 9833ce0ea..88b00f39a 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "3.2.0", + "version": "4.0.0", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-angular/package.json b/packages/dockview-angular/package.json index 078f3f2a2..63f6b6b58 100644 --- a/packages/dockview-angular/package.json +++ b/packages/dockview-angular/package.json @@ -1,6 +1,6 @@ { "name": "dockview-angular", - "version": "3.2.0", + "version": "4.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^3.2.0" + "dockview-core": "^4.0.0" } } diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index 1d99a5495..8fab699a9 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "3.2.0", + "version": "4.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", diff --git a/packages/dockview-react/package.json b/packages/dockview-react/package.json index ce38e89b7..6d5fa2b75 100644 --- a/packages/dockview-react/package.json +++ b/packages/dockview-react/package.json @@ -1,6 +1,6 @@ { "name": "dockview-react", - "version": "3.2.0", + "version": "4.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage" }, "dependencies": { - "dockview": "^3.2.0" + "dockview": "^4.0.0" } } diff --git a/packages/dockview-vue/package.json b/packages/dockview-vue/package.json index 0eca82496..dfa4dedaf 100644 --- a/packages/dockview-vue/package.json +++ b/packages/dockview-vue/package.json @@ -1,6 +1,6 @@ { "name": "dockview-vue", - "version": "3.2.0", + "version": "4.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -52,7 +52,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage" }, "dependencies": { - "dockview-core": "^3.2.0" + "dockview-core": "^4.0.0" }, "peerDependencies": { "vue": "^3.4.0" diff --git a/packages/dockview/package.json b/packages/dockview/package.json index 312cd1a43..51b0dd5b9 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "3.2.0", + "version": "4.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,7 +54,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^3.2.0" + "dockview-core": "^4.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/packages/docs/package.json b/packages/docs/package.json index e2ec4b807..2a0cef3d2 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "3.2.0", + "version": "4.0.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": "^3.2.0", + "dockview": "^4.0.0", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", "react-laag": "^2.0.5",