From 14f64f170bb146eff6543c7eb3bfad9715337a2e Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Thu, 23 Nov 2023 22:27:00 +0000 Subject: [PATCH] feat: gready render mode --- .codesandbox/ci.json | 3 +- .../__tests__/api/dockviewPanelApi.spec.ts | 8 +- .../dockview/components/panel/content.spec.ts | 12 +- .../dockview/dockviewGroupPanelModel.spec.ts | 12 +- .../__tests__/dockview/dockviewPanel.spec.ts | 24 ++- .../dockview-core/src/api/dockviewPanelApi.ts | 24 ++- packages/dockview-core/src/dnd/dnd.ts | 50 +++-- packages/dockview-core/src/dnd/droptarget.ts | 175 +++++++++--------- .../components/greadyReadyContainer.scss | 15 ++ .../components/greadyRenderContainer.ts | 133 +++++++++++++ .../src/dockview/components/panel/content.ts | 154 ++++++++++++--- .../src/dockview/deserializer.ts | 13 +- .../src/dockview/dockviewComponent.ts | 23 ++- .../src/dockview/dockviewGroupPanelModel.ts | 62 ++----- .../src/dockview/dockviewPanel.ts | 29 ++- .../dockview-core/src/dockview/options.ts | 4 + packages/dockview-core/src/dockview/types.ts | 2 + packages/dockview-core/src/dom.ts | 15 ++ packages/dockview-core/src/index.ts | 8 +- packages/dockview/src/dockview/dockview.tsx | 5 + packages/docs/docs/components/dockview.mdx | 21 ++- .../docs/sandboxes/demo-dockview/src/app.tsx | 83 +++++++-- .../rendermode-dockview/package.json | 33 ++++ .../rendermode-dockview/public/index.html | 45 +++++ .../rendermode-dockview/src/app.scss | 0 .../sandboxes/rendermode-dockview/src/app.tsx | 107 +++++++++++ .../rendermode-dockview/src/index.tsx | 20 ++ .../rendermode-dockview/src/styles.css | 16 ++ .../rendermode-dockview/tsconfig.json | 18 ++ 29 files changed, 895 insertions(+), 219 deletions(-) create mode 100644 packages/dockview-core/src/dockview/components/greadyReadyContainer.scss create mode 100644 packages/dockview-core/src/dockview/components/greadyRenderContainer.ts create mode 100644 packages/docs/sandboxes/rendermode-dockview/package.json create mode 100644 packages/docs/sandboxes/rendermode-dockview/public/index.html create mode 100644 packages/docs/sandboxes/rendermode-dockview/src/app.scss create mode 100644 packages/docs/sandboxes/rendermode-dockview/src/app.tsx create mode 100644 packages/docs/sandboxes/rendermode-dockview/src/index.tsx create mode 100644 packages/docs/sandboxes/rendermode-dockview/src/styles.css create mode 100644 packages/docs/sandboxes/rendermode-dockview/tsconfig.json diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index e5e5db007..53e2d4776 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -23,6 +23,7 @@ "/packages/docs/sandboxes/nativeapp-dockview", "/packages/docs/sandboxes/nested-dockview", "/packages/docs/sandboxes/rendering-dockview", + "/packages/docs/sandboxes/rendermode-dockview", "/packages/docs/sandboxes/resize-dockview", "/packages/docs/sandboxes/resizecontainer-dockview", "/packages/docs/sandboxes/simple-dockview", @@ -37,4 +38,4 @@ "/packages/docs/sandboxes/javascript/vanilla-dockview" ], "node": "18" -} \ No newline at end of file +} diff --git a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts index 475146b5f..1d610ba99 100644 --- a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts +++ b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts @@ -36,7 +36,7 @@ describe('groupPanelApi', () => { }); test('updateParameters', () => { - const groupPanel: Partial = { + const groupPanel: Partial = { id: 'test_id', update: jest.fn(), }; @@ -53,7 +53,7 @@ describe('groupPanelApi', () => { ); const cut = new DockviewPanelApiImpl( - groupPanel, + groupPanel, groupViewPanel, accessor ); @@ -67,7 +67,7 @@ describe('groupPanelApi', () => { }); test('onDidGroupChange', () => { - const groupPanel: Partial = { + const groupPanel: Partial = { id: 'test_id', }; @@ -83,7 +83,7 @@ describe('groupPanelApi', () => { ); const cut = new DockviewPanelApiImpl( - groupPanel, + groupPanel, groupViewPanel, accessor ); diff --git a/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts index 34ba707a3..1b6d7b161 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts @@ -9,6 +9,7 @@ import { CompositeDisposable } from '../../../../lifecycle'; import { PanelUpdateEvent } from '../../../../panel/types'; import { IDockviewPanel } from '../../../../dockview/dockviewPanel'; import { IDockviewPanelModel } from '../../../../dockview/dockviewPanelModel'; +import { DockviewComponent } from '../../../../dockview/dockviewComponent'; class TestContentRenderer extends CompositeDisposable @@ -56,7 +57,14 @@ describe('contentContainer', () => { let blur = 0; const disposable = new CompositeDisposable(); - const cut = new ContentContainer(); + + const dockviewComponent = jest.fn(() => { + return { + renderingType: 'destructive', + } as DockviewComponent; + }); + + const cut = new ContentContainer(dockviewComponent(), jest.fn() as any); disposable.addDisposables( cut.onDidFocus(() => { @@ -73,6 +81,7 @@ describe('contentContainer', () => { view: { content: contentRenderer, } as Partial, + api: { renderingType: 'destructive' }, } as Partial; cut.openPanel(panel as IDockviewPanel); @@ -107,6 +116,7 @@ describe('contentContainer', () => { view: { content: contentRenderer2, } as Partial, + api: { renderingType: 'destructive' }, } as Partial; cut.openPanel(panel2 as IDockviewPanel); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts index 2052ef1be..b73c49c5d 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts @@ -576,19 +576,25 @@ describe('groupview', () => { .getElementsByClassName('content-container') .item(0)!.childNodes; - const panel1 = new TestPanel('id_1', null as any); + const panel1 = new TestPanel('id_1', { + renderingType: 'destructive', + } as any); cut.openPanel(panel1); expect(contentContainer.length).toBe(1); expect(contentContainer.item(0)).toBe(panel1.view.content.element); - const panel2 = new TestPanel('id_2', null as any); + const panel2 = new TestPanel('id_2', { + renderingType: 'destructive', + } as any); cut.openPanel(panel2); expect(contentContainer.length).toBe(1); expect(contentContainer.item(0)).toBe(panel2.view.content.element); - const panel3 = new TestPanel('id_2', null as any); + const panel3 = new TestPanel('id_2', { + renderingType: 'destructive', + } as any); cut.openPanel(panel3, { skipSetPanelActive: true }); expect(contentContainer.length).toBe(1); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewPanel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewPanel.spec.ts index c67546db8..b3ab5d7ad 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewPanel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewPanel.spec.ts @@ -29,7 +29,9 @@ describe('dockviewPanel', () => { const group = new groupMock(); const model = new panelModelMock(); - const cut = new DockviewPanel('fake-id', accessor, api, group, model); + const cut = new DockviewPanel('fake-id', accessor, api, group, model, { + renderingType: 'destructive', + }); let latestTitle: string | undefined = undefined; @@ -74,7 +76,9 @@ describe('dockviewPanel', () => { const group = new groupMock(); const model = new panelModelMock(); - const cut = new DockviewPanel('fake-id', accessor, api, group, model); + const cut = new DockviewPanel('fake-id', accessor, api, group, model, { + renderingType: 'destructive', + }); cut.init({ title: 'myTitle', params: {} }); expect(cut.title).toBe('myTitle'); @@ -109,7 +113,9 @@ describe('dockviewPanel', () => { const group = new groupMock(); const model = new panelModelMock(); - const cut = new DockviewPanel('fake-id', accessor, api, group, model); + const cut = new DockviewPanel('fake-id', accessor, api, group, model, { + renderingType: 'destructive', + }); cut.init({ params: {}, title: 'title' }); @@ -141,7 +147,9 @@ describe('dockviewPanel', () => { const group = new groupMock(); const model = new panelModelMock(); - const cut = new DockviewPanel('fake-id', accessor, api, group, model); + const cut = new DockviewPanel('fake-id', accessor, api, group, model, { + renderingType: 'destructive', + }); expect(cut.params).toEqual(undefined); @@ -177,7 +185,9 @@ describe('dockviewPanel', () => { const group = new groupMock(); const model = new panelModelMock(); - const cut = new DockviewPanel('fake-id', accessor, api, group, model); + const cut = new DockviewPanel('fake-id', accessor, api, group, model, { + renderingType: 'destructive', + }); cut.api.setSize({ height: 123, width: 456 }); @@ -208,7 +218,9 @@ describe('dockviewPanel', () => { const group = new groupMock(); const model = new panelModelMock(); - const cut = new DockviewPanel('fake-id', accessor, api, group, model); + const cut = new DockviewPanel('fake-id', accessor, api, group, model, { + renderingType: 'destructive', + }); cut.init({ params: { a: '1', b: '2' }, title: 'A title' }); expect(cut.params).toEqual({ a: '1', b: '2' }); diff --git a/packages/dockview-core/src/api/dockviewPanelApi.ts b/packages/dockview-core/src/api/dockviewPanelApi.ts index e724c251d..bc1101711 100644 --- a/packages/dockview-core/src/api/dockviewPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewPanelApi.ts @@ -2,14 +2,19 @@ import { Emitter, Event } from '../events'; import { GridviewPanelApiImpl, GridviewPanelApi } from './gridviewPanelApi'; import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel'; import { MutableDisposable } from '../lifecycle'; -import { IDockviewPanel } from '../dockview/dockviewPanel'; +import { DockviewPanel, IDockviewPanel } from '../dockview/dockviewPanel'; import { DockviewComponent } from '../dockview/dockviewComponent'; import { Position } from '../dnd/droptarget'; +import { RenderingType } from '../dockview/components/greadyRenderContainer'; export interface TitleEvent { readonly title: string; } +export interface RenderingTypeEvent { + renderingType: RenderingType; +} + /* * omit visibility modifiers since the visibility of a single group doesn't make sense * because it belongs to a groupview @@ -21,11 +26,14 @@ export interface DockviewPanelApi > { readonly group: DockviewGroupPanel; readonly isGroupActive: boolean; + readonly renderingType: RenderingType; readonly title: string | undefined; readonly onDidActiveGroupChange: Event; readonly onDidGroupChange: Event; + readonly onDidRendeingTypeChange: Event; close(): void; setTitle(title: string): void; + setRenderingType(renderingType: RenderingType): void; moveTo(options: { group: DockviewGroupPanel; position?: Position; @@ -48,6 +56,9 @@ export class DockviewPanelApiImpl private readonly _onDidGroupChange = new Emitter(); readonly onDidGroupChange = this._onDidGroupChange.event; + readonly _onDidRenderingTypeChange = new Emitter(); + readonly onDidRendeingTypeChange = this._onDidRenderingTypeChange.event; + private readonly disposable = new MutableDisposable(); get title(): string | undefined { @@ -58,6 +69,10 @@ export class DockviewPanelApiImpl return !!this.group?.isActive; } + get renderingType(): RenderingType { + return this.panel.renderingType; + } + set group(value: DockviewGroupPanel) { const isOldGroupActive = this.isGroupActive; @@ -81,7 +96,7 @@ export class DockviewPanelApiImpl } constructor( - private panel: IDockviewPanel, + private panel: DockviewPanel, group: DockviewGroupPanel, private readonly accessor: DockviewComponent ) { @@ -93,6 +108,7 @@ export class DockviewPanelApiImpl this.addDisposables( this.disposable, + this._onDidRenderingTypeChange, this._onDidTitleChange, this._onDidGroupChange, this._onDidActiveGroupChange @@ -117,6 +133,10 @@ export class DockviewPanelApiImpl this.panel.setTitle(title); } + setRenderingType(renderingType: RenderingType): void { + this.panel.setRenderingType(renderingType); + } + close(): void { this.group.model.closePanel(this.panel); } diff --git a/packages/dockview-core/src/dnd/dnd.ts b/packages/dockview-core/src/dnd/dnd.ts index 533be7e11..2120f2994 100644 --- a/packages/dockview-core/src/dnd/dnd.ts +++ b/packages/dockview-core/src/dnd/dnd.ts @@ -21,14 +21,43 @@ export class DragAndDropObserver extends CompositeDisposable { this.registerListeners(); } + onDragEnter(e: DragEvent): void { + this.target = e.target; + this.callbacks.onDragEnter(e); + } + + onDragOver(e: DragEvent): void { + e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome) + + if (this.callbacks.onDragOver) { + this.callbacks.onDragOver(e); + } + } + + onDragLeave(e: DragEvent): void { + if (this.target === e.target) { + this.target = null; + + this.callbacks.onDragLeave(e); + } + } + + onDragEnd(e: DragEvent): void { + this.target = null; + this.callbacks.onDragEnd(e); + } + + onDrop(e: DragEvent): void { + this.callbacks.onDrop(e); + } + private registerListeners(): void { this.addDisposables( addDisposableListener( this.element, 'dragenter', (e: DragEvent) => { - this.target = e.target; - this.callbacks.onDragEnter(e); + this.onDragEnter(e); }, true ) @@ -39,11 +68,7 @@ export class DragAndDropObserver extends CompositeDisposable { this.element, 'dragover', (e: DragEvent) => { - e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome) - - if (this.callbacks.onDragOver) { - this.callbacks.onDragOver(e); - } + this.onDragOver(e); }, true ) @@ -51,24 +76,19 @@ export class DragAndDropObserver extends CompositeDisposable { this.addDisposables( addDisposableListener(this.element, 'dragleave', (e: DragEvent) => { - if (this.target === e.target) { - this.target = null; - - this.callbacks.onDragLeave(e); - } + this.onDragLeave(e); }) ); this.addDisposables( addDisposableListener(this.element, 'dragend', (e: DragEvent) => { - this.target = null; - this.callbacks.onDragEnd(e); + this.onDragEnd(e); }) ); this.addDisposables( addDisposableListener(this.element, 'drop', (e: DragEvent) => { - this.callbacks.onDrop(e); + this.onDrop(e); }) ); } diff --git a/packages/dockview-core/src/dnd/droptarget.ts b/packages/dockview-core/src/dnd/droptarget.ts index 003f8b045..357d4c32f 100644 --- a/packages/dockview-core/src/dnd/droptarget.ts +++ b/packages/dockview-core/src/dnd/droptarget.ts @@ -63,6 +63,8 @@ export class Droptarget extends CompositeDisposable { private readonly _onDrop = new Emitter(); readonly onDrop: Event = this._onDrop.event; + readonly dnd: DragAndDropObserver; + private static USED_EVENT_ID = '__dockview_droptarget_event_is_used__'; get state(): Position | undefined { @@ -90,98 +92,97 @@ export class Droptarget extends CompositeDisposable { this.options.acceptedTargetZones ); - this.addDisposables( - this._onDrop, - new DragAndDropObserver(this.element, { - onDragEnter: () => undefined, - onDragOver: (e) => { - if (this._acceptedTargetZonesSet.size === 0) { - this.removeDropTarget(); - return; - } - - const width = this.element.clientWidth; - const height = this.element.clientHeight; - - if (width === 0 || height === 0) { - return; // avoid div!0 - } - - const rect = ( - e.currentTarget as HTMLElement - ).getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; - - const quadrant = this.calculateQuadrant( - this._acceptedTargetZonesSet, - x, - y, - width, - height - ); - - /** - * If the event has already been used by another DropTarget instance - * then don't show a second drop target, only one target should be - * active at any one time - */ - if (this.isAlreadyUsed(e) || quadrant === null) { - // no drop target should be displayed - this.removeDropTarget(); - return; - } + this.dnd = new DragAndDropObserver(this.element, { + onDragEnter: () => undefined, + onDragOver: (e) => { + if (this._acceptedTargetZonesSet.size === 0) { + this.removeDropTarget(); + return; + } + + const width = this.element.clientWidth; + const height = this.element.clientHeight; + + if (width === 0 || height === 0) { + return; // avoid div!0 + } + + const rect = ( + e.currentTarget as HTMLElement + ).getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + const quadrant = this.calculateQuadrant( + this._acceptedTargetZonesSet, + x, + y, + width, + height + ); + + /** + * If the event has already been used by another DropTarget instance + * then don't show a second drop target, only one target should be + * active at any one time + */ + if (this.isAlreadyUsed(e) || quadrant === null) { + // no drop target should be displayed + this.removeDropTarget(); + return; + } - if (typeof this.options.canDisplayOverlay === 'boolean') { - if (!this.options.canDisplayOverlay) { - this.removeDropTarget(); - return; - } - } else if (!this.options.canDisplayOverlay(e, quadrant)) { + if (typeof this.options.canDisplayOverlay === 'boolean') { + if (!this.options.canDisplayOverlay) { this.removeDropTarget(); return; } - - this.markAsUsed(e); - - if (!this.targetElement) { - this.targetElement = document.createElement('div'); - this.targetElement.className = 'drop-target-dropzone'; - this.overlayElement = document.createElement('div'); - this.overlayElement.className = 'drop-target-selection'; - this._state = 'center'; - this.targetElement.appendChild(this.overlayElement); - - this.element.classList.add('drop-target'); - this.element.append(this.targetElement); - } - - this.toggleClasses(quadrant, width, height); - - this.setState(quadrant); - }, - onDragLeave: () => { - this.removeDropTarget(); - }, - onDragEnd: () => { + } else if (!this.options.canDisplayOverlay(e, quadrant)) { this.removeDropTarget(); - }, - onDrop: (e) => { - e.preventDefault(); - - const state = this._state; - - this.removeDropTarget(); - - if (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: state, nativeEvent: e }); - } - }, - }) - ); + return; + } + + this.markAsUsed(e); + + if (!this.targetElement) { + this.targetElement = document.createElement('div'); + this.targetElement.className = 'drop-target-dropzone'; + this.overlayElement = document.createElement('div'); + this.overlayElement.className = 'drop-target-selection'; + this._state = 'center'; + this.targetElement.appendChild(this.overlayElement); + + this.element.classList.add('drop-target'); + this.element.append(this.targetElement); + } + + this.toggleClasses(quadrant, width, height); + + this.setState(quadrant); + }, + onDragLeave: () => { + this.removeDropTarget(); + }, + onDragEnd: () => { + this.removeDropTarget(); + }, + onDrop: (e) => { + e.preventDefault(); + + const state = this._state; + + this.removeDropTarget(); + + if (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: state, nativeEvent: e }); + } + }, + }); + + this.addDisposables(this._onDrop, this.dnd); } setTargetZones(acceptedTargetZones: Position[]): void { diff --git a/packages/dockview-core/src/dockview/components/greadyReadyContainer.scss b/packages/dockview-core/src/dockview/components/greadyReadyContainer.scss new file mode 100644 index 000000000..cf67afd19 --- /dev/null +++ b/packages/dockview-core/src/dockview/components/greadyReadyContainer.scss @@ -0,0 +1,15 @@ +.dv-render-overlay { + position: absolute; + z-index: 1; + + &.dv-render-overlay-float { + z-index: 999; + } +} + +.dv-debug { + .dv-render-overlay { + outline: 1px solid red; + outline-offset: -1; + } +} diff --git a/packages/dockview-core/src/dockview/components/greadyRenderContainer.ts b/packages/dockview-core/src/dockview/components/greadyRenderContainer.ts new file mode 100644 index 000000000..419a7c91c --- /dev/null +++ b/packages/dockview-core/src/dockview/components/greadyRenderContainer.ts @@ -0,0 +1,133 @@ +import { DragAndDropObserver } from '../../dnd/dnd'; +import { Droptarget } from '../../dnd/droptarget'; +import { getDomNodePagePosition, toggleClass } from '../../dom'; +import { CompositeDisposable, Disposable, IDisposable } from '../../lifecycle'; +import { IDockviewPanel } from '../dockviewPanel'; + +export type RenderingType = 'destructive' | 'gready'; + +export interface IRenderable { + readonly element: HTMLElement; + readonly dropTarget: Droptarget; +} + +export class GreadyRenderContainer extends CompositeDisposable { + private readonly map: Record = {}; + + get allIds(): string[] { + return Object.keys(this.map); + } + + constructor(private readonly element: HTMLElement) { + super(); + + this.addDisposables({ + dispose: () => { + for (const value of Object.values(this.map)) { + value.dispose(); + } + }, + }); + } + + remove(panel: IDockviewPanel): boolean { + if (this.map[panel.api.id]) { + this.map[panel.api.id].dispose(); + delete this.map[panel.api.id]; + return true; + } + return false; + } + + setReferenceContentContainer( + panel: IDockviewPanel, + referenceContainer: IRenderable + ) { + if (!this.map[panel.api.id]) { + this.map[panel.api.id] = Disposable.NONE; + } + + this.map[panel.api.id]?.dispose(); + + if (panel.view.content.element.parentElement !== this.element) { + this.element.appendChild(panel.view.content.element); + } + + const resize = () => { + const box = getDomNodePagePosition(referenceContainer.element); + const box2 = getDomNodePagePosition(this.element); + panel.view.content.element.style.left = `${box.left - box2.left}px`; + panel.view.content.element.style.top = `${box.top - box2.top}px`; + panel.view.content.element.style.width = `${box.width}px`; + panel.view.content.element.style.height = `${box.height}px`; + + toggleClass( + panel.view.content.element, + 'dv-render-overlay-float', + panel.group.api.isFloating + ); + }; + + const disposable = new CompositeDisposable( + /** + * dnd will not work as expected unless we explictly forward those events from the + * view to the underlying container + */ + new DragAndDropObserver(panel.view.content.element, { + onDragEnd: (e) => { + referenceContainer.dropTarget.dnd.onDragEnd(e); + }, + onDragEnter: (e) => { + referenceContainer.dropTarget.dnd.onDragEnter(e); + }, + onDragLeave: (e) => { + referenceContainer.dropTarget.dnd.onDragLeave(e); + }, + onDrop: (e) => { + referenceContainer.dropTarget.dnd.onDrop(e); + }, + onDragOver: (e) => { + referenceContainer.dropTarget.dnd.onDragOver(e); + }, + }), + panel.api.onDidVisibilityChange((event) => { + panel.view.content.element.style.display = event.isVisible + ? '' + : 'none'; + }), + panel.api.onDidDimensionsChange((event) => { + resize(); + }), + { + dispose: () => { + panel.view.content.element.style.display = ''; + panel.view.content.element.style.left = ''; + panel.view.content.element.style.top = ''; + panel.view.content.element.style.width = ''; + panel.view.content.element.style.height = ''; + + this.element.removeChild(panel.view.content.element); + + toggleClass( + panel.view.content.element, + 'dv-render-overlay', + false + ); + toggleClass( + panel.view.content.element, + 'dv-render-overlay-float', + false + ); + }, + } + ); + + toggleClass(panel.view.content.element, 'dv-render-overlay', true); + + queueMicrotask(() => { + resize(); + }); + + this.map[panel.api.id] = disposable; + } +} diff --git a/packages/dockview-core/src/dockview/components/panel/content.ts b/packages/dockview-core/src/dockview/components/panel/content.ts index 9934c5ec4..663c152b7 100644 --- a/packages/dockview-core/src/dockview/components/panel/content.ts +++ b/packages/dockview-core/src/dockview/components/panel/content.ts @@ -1,13 +1,21 @@ import { CompositeDisposable, + Disposable, IDisposable, MutableDisposable, } from '../../../lifecycle'; import { Emitter, Event } from '../../../events'; import { trackFocus } from '../../../dom'; import { IDockviewPanel } from '../../dockviewPanel'; +import { DockviewComponent } from '../../dockviewComponent'; +import { DragAndDropObserver } from '../../../dnd/dnd'; +import { Droptarget } from '../../../dnd/droptarget'; +import { DockviewGroupPanelModel } from '../../dockviewGroupPanelModel'; +import { getPanelData } from '../../../dnd/dataTransfer'; +import { DockviewDropTargets } from '../../types'; export interface IContentContainer extends IDisposable { + readonly dropTarget: Droptarget; onDidFocus: Event; onDidBlur: Event; element: HTMLElement; @@ -16,6 +24,7 @@ export interface IContentContainer extends IDisposable { closePanel: () => void; show(): void; hide(): void; + renderPanel(panel: IDockviewPanel): void; } export class ContentContainer @@ -36,7 +45,12 @@ export class ContentContainer return this._element; } - constructor() { + readonly dropTarget: Droptarget; + + constructor( + private readonly accessor: DockviewComponent, + private readonly group: DockviewGroupPanelModel + ) { super(); this._element = document.createElement('div'); this._element.className = 'content-container'; @@ -49,6 +63,51 @@ export class ContentContainer // 2) register window dragStart events to disable pointer events // 3) register dragEnd events // 4) register mouseMove events (if no buttons are present we take this as a dragEnd event) + + this.dropTarget = new Droptarget(this.element, { + acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'], + canDisplayOverlay: (event, position) => { + if ( + this.group.locked === 'no-drop-target' || + (this.group.locked && position === 'center') + ) { + return false; + } + + const data = getPanelData(); + + if (!data && event.shiftKey && !this.group.isFloating) { + return false; + } + + 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 this.group.canDisplayOverlay( + event, + position, + DockviewDropTargets.Panel + ); + }, + }); + + this.addDisposables(this.dropTarget); } show(): void { @@ -59,44 +118,85 @@ export class ContentContainer this.element.style.display = 'none'; } + renderPanel(panel: IDockviewPanel): void { + const isActive = panel === this.group.activePanel; + + switch (panel.api.renderingType) { + case 'destructive': + this.accessor.greadyRenderContainer.remove(panel); + if (isActive) { + if (this.panel) { + this._element.appendChild( + this.panel.view.content.element + ); + } + } + break; + case 'gready': + if ( + panel.view.content.element.parentElement === this._element + ) { + this._element.removeChild(panel.view.content.element); + } + this.accessor.greadyRenderContainer.setReferenceContentContainer( + panel, + this + ); + break; + } + } + public openPanel(panel: IDockviewPanel): void { if (this.panel === panel) { return; } - if (this.panel) { - if (this.panel.view?.content) { - this._element.removeChild(this.panel.view.content.element); - } - this.panel = undefined; + + const panelRenderingType = panel.api.renderingType; + + if ( + this.panel && + this.panel.view.content.element.parentElement === this._element + ) { + /** + * If the currently attached panel is mounted directly to the content then remove it + */ + this._element.removeChild(this.panel.view.content.element); } + this.panel = panel; const disposable = new CompositeDisposable(); - if (this.panel.view) { - const _onDidFocus = this.panel.view.content.onDidFocus; - const _onDidBlur = this.panel.view.content.onDidBlur; + const _onDidFocus = this.panel.view.content.onDidFocus; + const _onDidBlur = this.panel.view.content.onDidBlur; - const focusTracker = trackFocus(this._element); + const focusTracker = trackFocus(this._element); + disposable.addDisposables( + focusTracker, + focusTracker.onDidFocus(() => this._onDidFocus.fire()), + focusTracker.onDidBlur(() => this._onDidBlur.fire()) + ); + + if (_onDidFocus) { disposable.addDisposables( - focusTracker, - focusTracker.onDidFocus(() => this._onDidFocus.fire()), - focusTracker.onDidBlur(() => this._onDidBlur.fire()) + _onDidFocus(() => this._onDidFocus.fire()) ); + } + if (_onDidBlur) { + disposable.addDisposables(_onDidBlur(() => this._onDidBlur.fire())); + } - if (_onDidFocus) { - disposable.addDisposables( - _onDidFocus(() => this._onDidFocus.fire()) - ); - } - if (_onDidBlur) { - disposable.addDisposables( - _onDidBlur(() => this._onDidBlur.fire()) + switch (panelRenderingType) { + case 'gready': + this.accessor.greadyRenderContainer.setReferenceContentContainer( + panel, + this ); - } - - this._element.appendChild(this.panel.view.content.element); + break; + case 'destructive': + this._element.appendChild(this.panel.view.content.element); + break; } this.disposable.value = disposable; @@ -107,8 +207,10 @@ export class ContentContainer } public closePanel(): void { - if (this.panel?.view?.content?.element) { - this._element.removeChild(this.panel.view.content.element); + if (this.panel) { + if (this.accessor.options.renderingType === 'destructive') { + this._element.removeChild(this.panel.view.content.element); + } this.panel = undefined; } } diff --git a/packages/dockview-core/src/dockview/deserializer.ts b/packages/dockview-core/src/dockview/deserializer.ts index b8f308038..def333c37 100644 --- a/packages/dockview-core/src/dockview/deserializer.ts +++ b/packages/dockview-core/src/dockview/deserializer.ts @@ -21,7 +21,7 @@ interface LegacyState extends GroupviewPanelState { } export class DefaultDockviewDeserialzier implements IPanelDeserializer { - constructor(private readonly layout: DockviewComponent) {} + constructor(private readonly accessor: DockviewComponent) {} public fromJSON( panelData: GroupviewPanelState, @@ -41,7 +41,7 @@ export class DefaultDockviewDeserialzier implements IPanelDeserializer { : panelData.tabComponent; const view = new DockviewPanelModel( - this.layout, + this.accessor, panelId, contentComponent, tabComponent @@ -49,10 +49,13 @@ export class DefaultDockviewDeserialzier implements IPanelDeserializer { const panel = new DockviewPanel( panelId, - this.layout, - new DockviewApi(this.layout), + this.accessor, + new DockviewApi(this.accessor), group, - view + view, + { + renderingType: panelData.renderingType, + } ); panel.init({ diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index e7ab959c2..0db1110ae 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -55,6 +55,10 @@ import { GroupDragEvent, TabDragEvent, } from './components/titlebar/tabsContainer'; +import { + GreadyRenderContainer, + RenderingType, +} from './components/greadyRenderContainer'; const DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE = 100; @@ -245,6 +249,8 @@ export class DockviewComponent private _options: Exclude; private watermark: IWatermarkRenderer | null = null; + readonly greadyRenderContainer: GreadyRenderContainer; + private readonly _onWillDragPanel = new Emitter(); readonly onWillDragPanel: Event = this._onWillDragPanel.event; @@ -299,6 +305,10 @@ export class DockviewComponent return activeGroup.activePanel; } + get renderingType(): RenderingType { + return this.options.renderingType ?? 'destructive'; + } + constructor(options: DockviewComponentOptions) { super({ proportionalLayout: true, @@ -308,9 +318,17 @@ export class DockviewComponent disableAutoResizing: options.disableAutoResizing, }); + const gready = document.createElement('div'); + gready.style.position = 'relative'; + this.gridview.element.appendChild(gready); + + this.greadyRenderContainer = new GreadyRenderContainer(gready); + toggleClass(this.gridview.element, 'dv-dockview', true); + toggleClass(this.element, 'dv-debug', !!options.debug); this.addDisposables( + this.greadyRenderContainer, this._onWillDragPanel, this._onWillDragGroup, this._onDidActivePanelChange, @@ -1041,6 +1059,7 @@ export class DockviewComponent group.model.removePanel(panel); if (!options.skipDispose) { + this.greadyRenderContainer.remove(panel); panel.dispose(); } @@ -1463,8 +1482,10 @@ export class DockviewComponent this, this._api, group, - view + view, + { renderingType: options.renderingType } ); + panel.init({ title: options.title ?? options.id, params: options?.params ?? {}, diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 43ee03b01..cfe10ac6c 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -136,7 +136,7 @@ export class DockviewGroupPanelModel { private readonly tabsContainer: ITabsContainer; private readonly contentContainer: IContentContainer; - private readonly dropTarget: Droptarget; + // private readonly dropTarget: Droptarget; private _activePanel: IDockviewPanel | undefined; private watermark?: IWatermarkRenderer; private _isGroupActive = false; @@ -248,7 +248,7 @@ export class DockviewGroupPanelModel set isFloating(value: boolean) { this._isFloating = value; - this.dropTarget.setTargetZones( + this.contentContainer.dropTarget.setTargetZones( value ? ['center'] : ['top', 'bottom', 'left', 'right', 'center'] ); @@ -272,49 +272,7 @@ export class DockviewGroupPanelModel this.tabsContainer = new TabsContainer(this.accessor, this.groupPanel); - this.contentContainer = new ContentContainer(); - - this.dropTarget = new Droptarget(this.contentContainer.element, { - acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'], - canDisplayOverlay: (event, position) => { - if ( - this.locked === 'no-drop-target' || - (this.locked && position === 'center') - ) { - return false; - } - - const data = getPanelData(); - - if (!data && event.shiftKey && !this.isFloating) { - return false; - } - - if (data && data.viewId === this.accessor.id) { - if (data.groupId === this.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._panels.length === 1 && data.groupId === this.id; - - return !groupHasOnePanelAndIsActiveDragElement; - } - - return this.canDisplayOverlay( - event, - position, - DockviewDropTargets.Panel - ); - }, - }); + this.contentContainer = new ContentContainer(this.accessor, this); container.append( this.tabsContainer.element, @@ -342,7 +300,7 @@ export class DockviewGroupPanelModel this.contentContainer.onDidBlur(() => { // noop }), - this.dropTarget.onDrop((event) => { + this.contentContainer.dropTarget.onDrop((event) => { this.handleDropEvent(event.nativeEvent, event.position); }), this._onMove, @@ -416,6 +374,10 @@ export class DockviewGroupPanelModel } } + rerender(panel: IDockviewPanel): void { + this.contentContainer.renderPanel(panel); + } + public indexOf(panel: IDockviewPanel): number { return this.tabsContainer.indexOf(panel.id); } @@ -687,15 +649,15 @@ export class DockviewGroupPanelModel const existingPanel = this._panels.indexOf(panel); const hasExistingPanel = existingPanel > -1; + this.tabsContainer.show(); + this.contentContainer.show(); + this.tabsContainer.openPanel(panel, index); if (!skipSetActive) { this.contentContainer.openPanel(panel); } - this.tabsContainer.show(); - this.contentContainer.show(); - if (hasExistingPanel) { // TODO - need to ensure ordering hasn't changed and if it has need to re-order this.panels return; @@ -849,7 +811,7 @@ export class DockviewGroupPanelModel panel.dispose(); } - this.dropTarget.dispose(); + // this.dropTarget.dispose(); this.tabsContainer.dispose(); this.contentContainer.dispose(); } diff --git a/packages/dockview-core/src/dockview/dockviewPanel.ts b/packages/dockview-core/src/dockview/dockviewPanel.ts index 080abf1cf..c45b54ff5 100644 --- a/packages/dockview-core/src/dockview/dockviewPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanel.ts @@ -9,6 +9,7 @@ import { CompositeDisposable, IDisposable } from '../lifecycle'; import { IPanel, PanelUpdateEvent, Parameters } from '../panel/types'; import { IDockviewPanelModel } from './dockviewPanelModel'; import { DockviewComponent } from './dockviewComponent'; +import { RenderingType } from './components/greadyRenderContainer'; export interface IDockviewPanel extends IDisposable, IPanel { readonly view: IDockviewPanelModel; @@ -28,10 +29,11 @@ export class DockviewPanel implements IDockviewPanel { readonly api: DockviewPanelApiImpl; + private _group: DockviewGroupPanel; private _params?: Parameters; - private _title: string | undefined; + private _renderingType: RenderingType | undefined; get params(): Parameters | undefined { return this._params; @@ -45,14 +47,20 @@ export class DockviewPanel return this._group; } + get renderingType(): RenderingType { + return this._renderingType ?? this.accessor.renderingType; + } + constructor( public readonly id: string, - accessor: DockviewComponent, + private readonly accessor: DockviewComponent, private readonly containerApi: DockviewApi, group: DockviewGroupPanel, - readonly view: IDockviewPanelModel + readonly view: IDockviewPanelModel, + options: { renderingType?: RenderingType } ) { super(); + this._renderingType = options.renderingType; this._group = group; this.api = new DockviewPanelApiImpl(this, this._group, accessor); @@ -65,6 +73,9 @@ export class DockviewPanel // forward the resize event to the group since if you want to resize a panel // you are actually just resizing the panels parent which is the group this.group.api.setSize(event); + }), + this.api.onDidRendeingTypeChange((event) => { + this.group.model.rerender(this); }) ); } @@ -95,6 +106,7 @@ export class DockviewPanel ? this._params : undefined, title: this.title, + renderingType: this._renderingType, }; } @@ -114,6 +126,17 @@ export class DockviewPanel } } + setRenderingType(renderingType: RenderingType): void { + const didChange = renderingType !== this.renderingType; + + if (didChange) { + this._renderingType = renderingType; + this.api._onDidRenderingTypeChange.fire({ + renderingType, + }); + } + } + public update(event: PanelUpdateEvent): void { // merge the new parameters with the existing parameters this._params = { diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 8bd23025a..088626eb3 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -20,6 +20,7 @@ import { FrameworkFactory, } from '../panel/componentFactory'; import { DockviewGroupPanelApi } from '../api/dockviewGroupPanelApi'; +import { RenderingType } from './components/greadyRenderContainer'; export interface IHeaderActionsRenderer extends IDisposable { readonly element: HTMLElement; @@ -96,6 +97,8 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions { minimumHeightWithinViewport?: number; minimumWidthWithinViewport?: number; }; + renderingType?: 'gready' | 'destructive'; + debug?: boolean; } export interface PanelOptions

{ @@ -168,6 +171,7 @@ export type AddPanelOptions

= Omit< > & { component: string; tabComponent?: string; + renderingType?: RenderingType; } & Partial; type AddGroupOptionsWithPanel = { diff --git a/packages/dockview-core/src/dockview/types.ts b/packages/dockview-core/src/dockview/types.ts index 7d870746c..18ee67914 100644 --- a/packages/dockview-core/src/dockview/types.ts +++ b/packages/dockview-core/src/dockview/types.ts @@ -5,6 +5,7 @@ import { DockviewApi } from '../api/component.api'; import { Event } from '../events'; import { Optional } from '../types'; import { DockviewGroupPanel, IDockviewGroupPanel } from './dockviewGroupPanel'; +import { RenderingType } from './components/greadyRenderContainer'; export enum DockviewDropTargets { Tab, @@ -91,5 +92,6 @@ export interface GroupviewPanelState { contentComponent?: string; tabComponent?: string; title?: string; + renderingType?: RenderingType; params?: { [key: string]: any }; } diff --git a/packages/dockview-core/src/dom.ts b/packages/dockview-core/src/dom.ts index a12b50742..12cac06e8 100644 --- a/packages/dockview-core/src/dom.ts +++ b/packages/dockview-core/src/dom.ts @@ -185,3 +185,18 @@ export function quasiPreventDefault(event: Event): void { export function quasiDefaultPrevented(event: Event): boolean { return (event as any)[QUASI_PREVENT_DEFAULT_KEY]; } + +export function getDomNodePagePosition(domNode: Element): { + left: number; + top: number; + width: number; + height: number; +} { + const { left, top, width, height } = domNode.getBoundingClientRect(); + return { + left: left + window.scrollX, + top: top + window.scrollY, + width: width, + height: height, + }; +} diff --git a/packages/dockview-core/src/index.ts b/packages/dockview-core/src/index.ts index e85e335c0..c3f326272 100644 --- a/packages/dockview-core/src/index.ts +++ b/packages/dockview-core/src/index.ts @@ -49,6 +49,8 @@ export * from './splitview/splitviewPanel'; export * from './paneview/paneviewPanel'; export * from './dockview/types'; +export { RenderingType } from './dockview/components/greadyRenderContainer'; + export { Position, positionToDirection, @@ -66,7 +68,11 @@ export { GridviewPanelApi, GridConstraintChangeEvent, } from './api/gridviewPanelApi'; -export { TitleEvent, DockviewPanelApi } from './api/dockviewPanelApi'; +export { + TitleEvent, + RenderingTypeEvent, + DockviewPanelApi, +} from './api/dockviewPanelApi'; export { PanelSizeEvent, PanelConstraintChangeEvent, diff --git a/packages/dockview/src/dockview/dockview.tsx b/packages/dockview/src/dockview/dockview.tsx index c9fd48b09..277306a35 100644 --- a/packages/dockview/src/dockview/dockview.tsx +++ b/packages/dockview/src/dockview/dockview.tsx @@ -10,6 +10,7 @@ import { ITabRenderer, DockviewGroupPanel, IHeaderActionsRenderer, + RenderingType, } from 'dockview-core'; import { ReactPanelContentPart } from './reactContentPart'; import { ReactPanelHeaderPart } from './reactHeaderPart'; @@ -76,6 +77,8 @@ export interface IDockviewReactProps { minimumHeightWithinViewport?: number; minimumWidthWithinViewport?: number; }; + debug?: boolean; + renderingType?: RenderingType; } const DEFAULT_REACT_TAB = 'props.defaultTabComponent'; @@ -175,6 +178,8 @@ export const DockviewReact = React.forwardRef( singleTabMode: props.singleTabMode, disableFloatingGroups: props.disableFloatingGroups, floatingGroupBounds: props.floatingGroupBounds, + renderingType: props.renderingType, + debug: props.debug, }); const { clientWidth, clientHeight } = domRef.current; diff --git a/packages/docs/docs/components/dockview.mdx b/packages/docs/docs/components/dockview.mdx index 6c9528f88..e933b222a 100644 --- a/packages/docs/docs/components/dockview.mdx +++ b/packages/docs/docs/components/dockview.mdx @@ -28,8 +28,9 @@ import DockviewWithIFrames from '@site/sandboxes/iframe-dockview/src/app'; import DockviewFloating from '@site/sandboxes/floatinggroup-dockview/src/app'; import DockviewLockedGroup from '@site/sandboxes/lockedgroup-dockview/src/app'; import DockviewKeyboard from '@site/sandboxes/keyboard-dockview/src/app'; +import DockviewRenderMode from '@site/sandboxes/rendermode-dockview/src/app'; -import { DocRef, Markdown } from '@site/src/components/ui/reference/docRef'; +import { DocRef } from '@site/src/components/ui/reference/docRef'; import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app'; import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simple-dockview/src/app'; @@ -816,6 +817,24 @@ api.group.api.setConstraints(...) react={DockviewConstraints} /> +## Render Mode + +Dockview has two rendering modes `destructive` (default) and `gready`. A rendering mode can be defined through the `renderingType` prop to `DockviewReact` or at an individual panel level when added where +the panel declaration takes precedence if both are defined. Rendering modes defined at the panel level are persisted, those defined at the `DockviewReact` level are not persisted. + +destructive +- Destructive mode is the default mode. In this mode when a panel is no longer visible through either it's visiblity being hidden or it not being the active panel within a group the panels HTMLElement is removed +from the DOM and any DOM state such as scrollbar positions will be lost. If you are using any ResizeObservers to measure size this will result both zero height and width as the HTMLElement no longer belongs to the DOM. +This design allows for maximum performance at some cost. +- Gready mode. In this mode when panels become hidden the HTMLElement is not destroyed so all DOM state such as scrollbar positions will be maintained. This is implemented by rendering each panel as an absolutely positioned +HTMLElement and hidden the HTMLElement with `display: none` when it should be hidden. + + + ## iFrames iFrames required special attention because of a particular behaviour in how iFrames render: diff --git a/packages/docs/sandboxes/demo-dockview/src/app.tsx b/packages/docs/sandboxes/demo-dockview/src/app.tsx index 43a1c779f..b1fdc9b0c 100644 --- a/packages/docs/sandboxes/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/demo-dockview/src/app.tsx @@ -5,15 +5,71 @@ import { IDockviewPanelHeaderProps, IDockviewPanelProps, IDockviewHeaderActionsProps, + DockviewPanelApi, + RenderingType, } from 'dockview'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { v4 } from 'uuid'; import './app.scss'; +const useRenderingType = ( + api: DockviewPanelApi +): [RenderingType, (value: RenderingType) => void] => { + const [mode, setMode] = React.useState(api.renderingType); + + React.useEffect(() => { + const disposable = api.onDidRendeingTypeChange((event) => { + setMode(event.renderingType); + }); + + return () => { + disposable.dispose(); + }; + }, []); + + const _setMode = React.useCallback( + (mode: RenderingType) => { + api.setRenderingType(mode); + }, + [api] + ); + + return [mode, _setMode]; +}; + const components = { default: (props: IDockviewPanelProps<{ title: string }>) => { - return

{props.params.title}
; + const [mode, setMode] = useRenderingType(props.api); + + return ( +
+
+
{props.api.title}
+ +
+ {mode} + +
+
+
+ ); }, }; @@ -233,18 +289,18 @@ const DockviewDemo = (props: { theme?: string }) => { title: 'Panel 4', position: { referencePanel: 'panel_3', direction: 'right' }, }); - event.api.addPanel({ - id: 'panel_5', - component: 'default', - title: 'Panel 5', - position: { referencePanel: 'panel_3', direction: 'below' }, - }); - event.api.addPanel({ - id: 'panel_6', - component: 'default', - title: 'Panel 6', - position: { referencePanel: 'panel_3', direction: 'right' }, - }); + // event.api.addPanel({ + // id: 'panel_5', + // component: 'default', + // title: 'Panel 5', + // position: { referencePanel: 'panel_3', direction: 'below' }, + // }); + // event.api.addPanel({ + // id: 'panel_6', + // component: 'default', + // title: 'Panel 6', + // position: { referencePanel: 'panel_3', direction: 'right' }, + // }); event.api.getPanel('panel_1')!.api.setActive(); @@ -260,6 +316,7 @@ const DockviewDemo = (props: { theme?: string }) => { prefixHeaderActionsComponent={PrefixHeaderControls} onReady={onReady} className={props.theme || 'dockview-theme-abyss'} + // debug={true} /> ); }; diff --git a/packages/docs/sandboxes/rendermode-dockview/package.json b/packages/docs/sandboxes/rendermode-dockview/package.json new file mode 100644 index 000000000..d1fcfa6ce --- /dev/null +++ b/packages/docs/sandboxes/rendermode-dockview/package.json @@ -0,0 +1,33 @@ +{ + "name": "rendermode-dockview", + "description": "", + "keywords": [ + "dockview" + ], + "version": "1.0.0", + "main": "src/index.tsx", + "dependencies": { + "dockview": "*", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "@types/uuid": "^9.0.0", + "typescript": "^4.9.5", + "react-scripts": "*" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/packages/docs/sandboxes/rendermode-dockview/public/index.html b/packages/docs/sandboxes/rendermode-dockview/public/index.html new file mode 100644 index 000000000..5a4850c1d --- /dev/null +++ b/packages/docs/sandboxes/rendermode-dockview/public/index.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + React App + + + + +
+ + + + diff --git a/packages/docs/sandboxes/rendermode-dockview/src/app.scss b/packages/docs/sandboxes/rendermode-dockview/src/app.scss new file mode 100644 index 000000000..e69de29bb diff --git a/packages/docs/sandboxes/rendermode-dockview/src/app.tsx b/packages/docs/sandboxes/rendermode-dockview/src/app.tsx new file mode 100644 index 000000000..cd173f7b1 --- /dev/null +++ b/packages/docs/sandboxes/rendermode-dockview/src/app.tsx @@ -0,0 +1,107 @@ +import { + DockviewReact, + DockviewReadyEvent, + IDockviewPanelProps, + DockviewPanelApi, + RenderingType, +} from 'dockview'; +import * as React from 'react'; +import './app.scss'; + +const useRenderingType = ( + api: DockviewPanelApi +): [RenderingType, (value: RenderingType) => void] => { + const [mode, setMode] = React.useState(api.renderingType); + + React.useEffect(() => { + const disposable = api.onDidRendeingTypeChange((event) => { + setMode(event.renderingType); + }); + + return () => { + disposable.dispose(); + }; + }, []); + + const _setMode = React.useCallback( + (mode: RenderingType) => { + api.setRenderingType(mode); + }, + [api] + ); + + return [mode, _setMode]; +}; + +const components = { + default: (props: IDockviewPanelProps<{ title: string }>) => { + const [mode, setMode] = useRenderingType(props.api); + + return ( +
+
+
{props.api.title}
+ +
+ {mode} + +
+
+
+ ); + }, +}; + +const DockviewDemo = (props: { theme?: string }) => { + const onReady = (event: DockviewReadyEvent) => { + event.api.addPanel({ + id: 'panel_1', + component: 'default', + title: 'Panel 1', + }); + event.api.addPanel({ + id: 'panel_2', + component: 'default', + title: 'Panel 2', + position: { referencePanel: 'panel_1', direction: 'within' }, + }); + event.api.addPanel({ + id: 'panel_3', + component: 'default', + title: 'Panel 3', + }); + + event.api.addPanel({ + id: 'panel_4', + component: 'default', + title: 'Panel 4', + position: { referencePanel: 'panel_3', direction: 'below' }, + }); + }; + + return ( + + ); +}; + +export default DockviewDemo; diff --git a/packages/docs/sandboxes/rendermode-dockview/src/index.tsx b/packages/docs/sandboxes/rendermode-dockview/src/index.tsx new file mode 100644 index 000000000..2fe1be232 --- /dev/null +++ b/packages/docs/sandboxes/rendermode-dockview/src/index.tsx @@ -0,0 +1,20 @@ +import { StrictMode } from 'react'; +import * as ReactDOMClient from 'react-dom/client'; +import './styles.css'; +import 'dockview/dist/styles/dockview.css'; + +import App from './app'; + +const rootElement = document.getElementById('root'); + +if (rootElement) { + const root = ReactDOMClient.createRoot(rootElement); + + root.render( + +
+ +
+
+ ); +} diff --git a/packages/docs/sandboxes/rendermode-dockview/src/styles.css b/packages/docs/sandboxes/rendermode-dockview/src/styles.css new file mode 100644 index 000000000..92b6a1b36 --- /dev/null +++ b/packages/docs/sandboxes/rendermode-dockview/src/styles.css @@ -0,0 +1,16 @@ +body { + margin: 0px; + color: white; + font-family: sans-serif; + text-align: center; +} + +#root { + height: 100vh; + width: 100vw; +} + +.app { + height: 100%; + +} diff --git a/packages/docs/sandboxes/rendermode-dockview/tsconfig.json b/packages/docs/sandboxes/rendermode-dockview/tsconfig.json new file mode 100644 index 000000000..cdc4fb5f5 --- /dev/null +++ b/packages/docs/sandboxes/rendermode-dockview/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "outDir": "build/dist", + "module": "esnext", + "target": "es5", + "lib": ["es6", "dom"], + "sourceMap": true, + "allowJs": true, + "jsx": "react-jsx", + "moduleResolution": "node", + "rootDir": "src", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "strictNullChecks": true + } +}