From 13d3db605b5e328d252940a4797cd4ac2682c758 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 3 Jun 2023 20:18:42 +0100 Subject: [PATCH 01/25] feat: add left header actions --- .../dockview/components/tab/defaultTab.scss | 25 ---------- .../components/titlebar/tabsContainer.ts | 47 ++++++++++++++----- .../src/dockview/dockviewComponent.ts | 5 +- .../src/dockview/dockviewGroupPanelModel.ts | 26 +++++++--- .../dockview-core/src/dockview/options.ts | 5 +- packages/dockview/src/dockview/dockview.tsx | 30 +++++++++--- packages/docs/docs/components/dockview.mdx | 29 ++++++------ .../groupcontrol-dockview/src/app.tsx | 4 +- 8 files changed, 102 insertions(+), 69 deletions(-) diff --git a/packages/dockview-core/src/dockview/components/tab/defaultTab.scss b/packages/dockview-core/src/dockview/components/tab/defaultTab.scss index d820534b8..81208377e 100644 --- a/packages/dockview-core/src/dockview/components/tab/defaultTab.scss +++ b/packages/dockview-core/src/dockview/components/tab/defaultTab.scss @@ -46,30 +46,5 @@ padding: 0px 8px; flex-grow: 1; } - - .action-container { - text-align: right; - display: flex; - - .tab-list { - display: flex; - padding: 0px; - margin: 0px; - justify-content: flex-end; - - .tab-action { - padding: 4px; - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - - &:hover { - border-radius: 2px; - background-color: var(--dv-icon-hover-background-color); - } - } - } - } } } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index debdf42e0..f1f2a9441 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -28,7 +28,8 @@ export interface ITabsContainer extends IDisposable { isActive: (tab: ITab) => boolean; closePanel: (panel: IDockviewPanel) => void; openPanel: (panel: IDockviewPanel, index?: number) => void; - setActionElement(element: HTMLElement | undefined): void; + setRightActionsElement(element: HTMLElement | undefined): void; + setLeftActionsElement(element: HTMLElement | undefined): void; hidden: boolean; show(): void; hide(): void; @@ -40,12 +41,14 @@ export class TabsContainer { private readonly _element: HTMLElement; private readonly tabContainer: HTMLElement; - private readonly actionContainer: HTMLElement; + private readonly rightActionsContainer: HTMLElement; + private readonly leftActionsContainer: HTMLElement; private readonly voidContainer: VoidContainer; private tabs: IValueDisposable[] = []; private selectedIndex = -1; - private actions: HTMLElement | undefined; + private rightActions: HTMLElement | undefined; + private leftActions: HTMLElement | undefined; private _hidden = false; @@ -79,17 +82,31 @@ export class TabsContainer this._element.style.display = 'none'; } - setActionElement(element: HTMLElement | undefined): void { - if (this.actions === element) { + setRightActionsElement(element: HTMLElement | undefined): void { + if (this.rightActions === element) { return; } - if (this.actions) { - this.actions.remove(); - this.actions = undefined; + if (this.rightActions) { + this.rightActions.remove(); + this.rightActions = undefined; } if (element) { - this.actionContainer.appendChild(element); - this.actions = element; + this.rightActionsContainer.appendChild(element); + this.rightActions = element; + } + } + + setLeftActionsElement(element: HTMLElement | undefined): void { + if (this.leftActions === element) { + return; + } + if (this.leftActions) { + this.leftActions.remove(); + this.leftActions = undefined; + } + if (element) { + this.leftActionsContainer.appendChild(element); + this.leftActions = element; } } @@ -146,8 +163,11 @@ export class TabsContainer }) ); - this.actionContainer = document.createElement('div'); - this.actionContainer.className = 'action-container'; + this.rightActionsContainer = document.createElement('div'); + this.rightActionsContainer.className = 'right-actions-container'; + + this.leftActionsContainer = document.createElement('div'); + this.leftActionsContainer.className = 'left-actions-container'; this.tabContainer = document.createElement('div'); this.tabContainer.className = 'tabs-container'; @@ -155,8 +175,9 @@ export class TabsContainer this.voidContainer = new VoidContainer(this.accessor, this.group); this._element.appendChild(this.tabContainer); + this._element.appendChild(this.leftActionsContainer); this._element.appendChild(this.voidContainer.element); - this._element.appendChild(this.actionContainer); + this._element.appendChild(this.rightActionsContainer); this.addDisposables( this.voidContainer, diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 6d58c0b67..f837fbdf3 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -71,7 +71,8 @@ export type DockviewComponentUpdateOptions = Pick< | 'showDndOverlay' | 'watermarkFrameworkComponent' | 'defaultTabComponent' - | 'createGroupControlElement' + | 'createLeftHeaderActionsElement' + | 'createRightHeaderActionsElement' >; export interface DockviewDropEvent extends GroupviewDropEvent { @@ -717,7 +718,7 @@ export class DockviewComponent if (itemId === undefined) { if (sourceGroup) { - this.moveGroup(sourceGroup, referenceGroup, target); + this.moveGroup(sourceGroup, referenceGroup, target); } return; } diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 5e9773a73..6e31cdce3 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -138,6 +138,7 @@ export class DockviewGroupPanelModel private _isGroupActive = false; private _locked = false; private _control: IGroupControlRenderer | undefined; + private _lhs: IGroupControlRenderer | undefined; private mostRecentlyUsed: IDockviewPanel[] = []; @@ -319,16 +320,29 @@ export class DockviewGroupPanelModel this.setActive(this.isActive, true, true); this.updateContainer(); - if (this.accessor.options.createGroupControlElement) { - this._control = this.accessor.options.createGroupControlElement( - this.groupPanel - ); + if (this.accessor.options.createRightHeaderActionsElement) { + this._control = + this.accessor.options.createRightHeaderActionsElement( + this.groupPanel + ); this.addDisposables(this._control); this._control.init({ containerApi: new DockviewApi(this.accessor), api: this.groupPanel.api, }); - this.tabsContainer.setActionElement(this._control.element); + this.tabsContainer.setRightActionsElement(this._control.element); + } + + if (this.accessor.options.createLeftHeaderActionsElement) { + this._lhs = this.accessor.options.createLeftHeaderActionsElement( + this.groupPanel + ); + this.addDisposables(this._lhs); + this._lhs.init({ + containerApi: new DockviewApi(this.accessor), + api: this.groupPanel.api, + }); + this.tabsContainer.setLeftActionsElement(this._lhs.element); } } @@ -511,7 +525,7 @@ export class DockviewGroupPanelModel } updateActions(element: HTMLElement | undefined): void { - this.tabsContainer.setActionElement(element); + this.tabsContainer.setRightActionsElement(element); } public setActive( diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 1e9b66db1..bab295646 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -79,7 +79,10 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions { styles?: ISplitviewStyles; defaultTabComponent?: string; showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean; - createGroupControlElement?: ( + createRightHeaderActionsElement?: ( + group: DockviewGroupPanel + ) => IGroupControlRenderer; + createLeftHeaderActionsElement?: ( group: DockviewGroupPanel ) => IGroupControlRenderer; singleTabMode?: 'fullwidth' | 'default'; diff --git a/packages/dockview/src/dockview/dockview.tsx b/packages/dockview/src/dockview/dockview.tsx index 8998c770d..e347a8aba 100644 --- a/packages/dockview/src/dockview/dockview.tsx +++ b/packages/dockview/src/dockview/dockview.tsx @@ -65,7 +65,8 @@ export interface IDockviewReactProps { className?: string; disableAutoResizing?: boolean; defaultTabComponent?: React.FunctionComponent; - groupControlComponent?: React.FunctionComponent; + rightHeaderActionsComponent?: React.FunctionComponent; + leftHeaderActionsComponent?: React.FunctionComponent; singleTabMode?: 'fullwidth' | 'default'; } @@ -150,10 +151,15 @@ export const DockviewReact = React.forwardRef( ? { separatorBorder: 'transparent' } : undefined, showDndOverlay: props.showDndOverlay, - createGroupControlElement: createGroupControlElement( - props.groupControlComponent, + createLeftHeaderActionsElement: createGroupControlElement( + props.leftHeaderActionsComponent, { addPortal } ), + createRightHeaderActionsElement: createGroupControlElement( + props.rightHeaderActionsComponent, + { addPortal } + ), + singleTabMode: props.singleTabMode, }); @@ -250,12 +256,24 @@ export const DockviewReact = React.forwardRef( return; } dockviewRef.current.updateOptions({ - createGroupControlElement: createGroupControlElement( - props.groupControlComponent, + createRightHeaderActionsElement: createGroupControlElement( + props.rightHeaderActionsComponent, { addPortal } ), }); - }, [props.groupControlComponent]); + }, [props.rightHeaderActionsComponent]); + + React.useEffect(() => { + if (!dockviewRef.current) { + return; + } + dockviewRef.current.updateOptions({ + createLeftHeaderActionsElement: createGroupControlElement( + props.leftHeaderActionsComponent, + { addPortal } + ), + }); + }, [props.leftHeaderActionsComponent]); return (
void | No | | | -| components | object | No | | | -| tabComponents | object | Yes | | | -| watermarkComponent | object | Yes | | | -| hideBorders | boolean | Yes | false | | -| className | string | Yes | '' | | -| disableAutoResizing | boolean | Yes | false | See Auto Resizing | -| onDidDrop | Event | Yes | false | | -| showDndOverlay | Event | Yes | false | | -| defaultTabComponent | object | Yes | | | -| groupControlComponent | object | Yes | | | -| singleTabMode | 'fullwidth' \| 'default' | Yes | 'default' | | +| Property | Type | Optional | Default | Description | +| --------------------------- | ------------------------------------ | -------- | --------- | ------------------------------------------------------------ | +| onReady | (event: SplitviewReadyEvent) => void | No | | | +| components | object | No | | | +| tabComponents | object | Yes | | | +| watermarkComponent | object | Yes | | | +| hideBorders | boolean | Yes | false | | +| className | string | Yes | '' | | +| disableAutoResizing | boolean | Yes | false | See Auto Resizing | +| onDidDrop | Event | Yes | false | | +| showDndOverlay | Event | Yes | false | | +| defaultTabComponent | object | Yes | | | +| leftHeaderActionsComponent | object | Yes | | | +| rightHeaderActionsComponent | object | Yes | | | +| singleTabMode | 'fullwidth' \| 'default' | Yes | 'default' | | ## Dockview API diff --git a/packages/docs/sandboxes/groupcontrol-dockview/src/app.tsx b/packages/docs/sandboxes/groupcontrol-dockview/src/app.tsx index fc4c868eb..0b24f7399 100644 --- a/packages/docs/sandboxes/groupcontrol-dockview/src/app.tsx +++ b/packages/docs/sandboxes/groupcontrol-dockview/src/app.tsx @@ -26,7 +26,7 @@ const components = { }, }; -const GroupControlComponent = (props: IDockviewGroupControlProps) => { +const RightHeaderActions = (props: IDockviewGroupControlProps) => { const isGroupActive = props.isGroupActive; const activePanel = props.activePanel; @@ -87,7 +87,7 @@ const DockviewGroupControl = () => { ); From d7baa93a9bccb83a788bb96c3cf310a9e1c7c036 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 3 Jun 2023 20:34:51 +0100 Subject: [PATCH 02/25] feat: left header actions --- .../components}/panel/content.spec.ts | 14 +- .../components}/tab.spec.ts | 10 +- .../titlebar/tabsContainer.spec.ts | 146 +++++++++++++++++- .../dockviewGroupPanelModel.spec.ts | 0 .../dockview/components/tab/defaultTab.scss | 25 +++ .../src/dockview/dockviewGroupPanelModel.ts | 31 ++-- .../dockview-core/src/dockview/options.ts | 6 +- ....spec.ts => headerActionsRenderer.spec.ts} | 6 +- .../dockview/groupControlsRenderer.spec.ts | 6 +- packages/dockview/src/dockview/dockview.tsx | 18 +-- ...lsRenderer.ts => headerActionsRenderer.ts} | 12 +- packages/dockview/src/index.ts | 2 +- packages/docs/docs/components/dockview.mdx | 6 +- .../docs/sandboxes/demo-dockview/src/app.tsx | 41 ++++- .../groupcontrol-dockview/src/app.tsx | 6 +- 15 files changed, 260 insertions(+), 69 deletions(-) rename packages/dockview-core/src/__tests__/{groupview => dockview/components}/panel/content.spec.ts (88%) rename packages/dockview-core/src/__tests__/{groupview => dockview/components}/tab.spec.ts (95%) rename packages/dockview-core/src/__tests__/{groupview => dockview/components}/titlebar/tabsContainer.spec.ts (68%) rename packages/dockview-core/src/__tests__/{groupview => dockview}/dockviewGroupPanelModel.spec.ts (100%) rename packages/dockview/src/__tests__/dockview/{groupControlsRenderer.spec.ts => headerActionsRenderer.spec.ts} (88%) rename packages/dockview/src/dockview/{groupControlsRenderer.ts => headerActionsRenderer.ts} (90%) diff --git a/packages/dockview-core/src/__tests__/groupview/panel/content.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts similarity index 88% rename from packages/dockview-core/src/__tests__/groupview/panel/content.spec.ts rename to packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts index 58de628a3..34ba707a3 100644 --- a/packages/dockview-core/src/__tests__/groupview/panel/content.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts @@ -1,14 +1,14 @@ import { fireEvent } from '@testing-library/dom'; -import { Emitter, Event } from '../../../events'; -import { ContentContainer } from '../../../dockview/components/panel/content'; +import { Emitter, Event } from '../../../../events'; +import { ContentContainer } from '../../../../dockview/components/panel/content'; import { GroupPanelContentPartInitParameters, IContentRenderer, -} from '../../../dockview/types'; -import { CompositeDisposable } from '../../../lifecycle'; -import { PanelUpdateEvent } from '../../../panel/types'; -import { IDockviewPanel } from '../../../dockview/dockviewPanel'; -import { IDockviewPanelModel } from '../../../dockview/dockviewPanelModel'; +} from '../../../../dockview/types'; +import { CompositeDisposable } from '../../../../lifecycle'; +import { PanelUpdateEvent } from '../../../../panel/types'; +import { IDockviewPanel } from '../../../../dockview/dockviewPanel'; +import { IDockviewPanelModel } from '../../../../dockview/dockviewPanelModel'; class TestContentRenderer extends CompositeDisposable diff --git a/packages/dockview-core/src/__tests__/groupview/tab.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts similarity index 95% rename from packages/dockview-core/src/__tests__/groupview/tab.spec.ts rename to packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts index 012732e73..2aa671d00 100644 --- a/packages/dockview-core/src/__tests__/groupview/tab.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts @@ -1,9 +1,9 @@ import { fireEvent } from '@testing-library/dom'; -import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer'; -import { DockviewComponent } from '../../dockview/dockviewComponent'; -import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; -import { DockviewGroupPanelModel } from '../../dockview/dockviewGroupPanelModel'; -import { Tab } from '../../dockview/components/tab/tab'; +import { LocalSelectionTransfer, PanelTransfer } from '../../../dnd/dataTransfer'; +import { DockviewComponent } from '../../../dockview/dockviewComponent'; +import { DockviewGroupPanel } from '../../../dockview/dockviewGroupPanel'; +import { DockviewGroupPanelModel } from '../../../dockview/dockviewGroupPanelModel'; +import { Tab } from '../../../dockview/components/tab/tab'; describe('tab', () => { test('that empty tab has inactive-tab class', () => { diff --git a/packages/dockview-core/src/__tests__/groupview/titlebar/tabsContainer.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts similarity index 68% rename from packages/dockview-core/src/__tests__/groupview/titlebar/tabsContainer.spec.ts rename to packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts index 0bcc96f50..77efaca4c 100644 --- a/packages/dockview-core/src/__tests__/groupview/titlebar/tabsContainer.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts @@ -1,13 +1,13 @@ -import { DockviewComponent } from '../../../dockview/dockviewComponent'; -import { TabsContainer } from '../../../dockview/components/titlebar/tabsContainer'; -import { fireEvent } from '@testing-library/dom'; import { LocalSelectionTransfer, PanelTransfer, -} from '../../../dnd/dataTransfer'; -import { TestPanel } from '../dockviewGroupPanelModel.spec'; -import { DockviewGroupPanelModel } from '../../../dockview/dockviewGroupPanelModel'; -import { DockviewGroupPanel } from '../../../dockview/dockviewGroupPanel'; +} from '../../../../dnd/dataTransfer'; +import { TabsContainer } from '../../../../dockview/components/titlebar/tabsContainer'; +import { DockviewComponent } from '../../../../dockview/dockviewComponent'; +import { DockviewGroupPanel } from '../../../../dockview/dockviewGroupPanel'; +import { DockviewGroupPanelModel } from '../../../../dockview/dockviewGroupPanelModel'; +import { fireEvent } from '@testing-library/dom'; +import { TestPanel } from '../../dockviewGroupPanelModel.spec'; describe('tabsContainer', () => { test('that an external event does not render a drop target and calls through to the group mode', () => { @@ -331,4 +331,136 @@ describe('tabsContainer', () => { cut.element.getElementsByClassName('drop-target-dropzone').length ).toBe(0); }); + + test('left actions', () => { + const accessorMock = jest.fn(() => { + return (>{ + options: {}, + onDidAddPanel: jest.fn(), + onDidRemovePanel: jest.fn(), + }) as DockviewComponent; + }); + + const groupPanelMock = jest.fn(() => { + return (>{}) as DockviewGroupPanel; + }); + + const accessor = new accessorMock(); + const groupPanel = new groupPanelMock(); + + const cut = new TabsContainer(accessor, groupPanel); + + let query = cut.element.querySelectorAll( + '.tabs-and-actions-container > .left-actions-container' + ); + + expect(query.length).toBe(1); + expect(query[0].children.length).toBe(0); + + // add left action + + const left = document.createElement('div'); + left.className = 'test-left-actions-element'; + cut.setLeftActionsElement(left); + + query = cut.element.querySelectorAll( + '.tabs-and-actions-container > .left-actions-container' + ); + expect(query.length).toBe(1); + expect(query[0].children.item(0)?.className).toBe( + 'test-left-actions-element' + ); + expect(query[0].children.length).toBe(1); + + // add left action + + const left2 = document.createElement('div'); + left2.className = 'test-left-actions-element-2'; + cut.setLeftActionsElement(left2); + + query = cut.element.querySelectorAll( + '.tabs-and-actions-container > .left-actions-container' + ); + expect(query.length).toBe(1); + expect(query[0].children.item(0)?.className).toBe( + 'test-left-actions-element-2' + ); + expect(query[0].children.length).toBe(1); + + // remove left action + + cut.setLeftActionsElement(undefined); + query = cut.element.querySelectorAll( + '.tabs-and-actions-container > .left-actions-container' + ); + + expect(query.length).toBe(1); + expect(query[0].children.length).toBe(0); + }); + + test('right actions', () => { + const accessorMock = jest.fn(() => { + return (>{ + options: {}, + onDidAddPanel: jest.fn(), + onDidRemovePanel: jest.fn(), + }) as DockviewComponent; + }); + + const groupPanelMock = jest.fn(() => { + return (>{}) as DockviewGroupPanel; + }); + + const accessor = new accessorMock(); + const groupPanel = new groupPanelMock(); + + const cut = new TabsContainer(accessor, groupPanel); + + let query = cut.element.querySelectorAll( + '.tabs-and-actions-container > .right-actions-container' + ); + + expect(query.length).toBe(1); + expect(query[0].children.length).toBe(0); + + // add right action + + const right = document.createElement('div'); + right.className = 'test-right-actions-element'; + cut.setRightActionsElement(right); + + query = cut.element.querySelectorAll( + '.tabs-and-actions-container > .right-actions-container' + ); + expect(query.length).toBe(1); + expect(query[0].children.item(0)?.className).toBe( + 'test-right-actions-element' + ); + expect(query[0].children.length).toBe(1); + + // add right action + + const right2 = document.createElement('div'); + right2.className = 'test-right-actions-element-2'; + cut.setRightActionsElement(right2); + + query = cut.element.querySelectorAll( + '.tabs-and-actions-container > .right-actions-container' + ); + expect(query.length).toBe(1); + expect(query[0].children.item(0)?.className).toBe( + 'test-right-actions-element-2' + ); + expect(query[0].children.length).toBe(1); + + // remove right action + + cut.setRightActionsElement(undefined); + query = cut.element.querySelectorAll( + '.tabs-and-actions-container > .right-actions-container' + ); + + expect(query.length).toBe(1); + expect(query[0].children.length).toBe(0); + }); }); diff --git a/packages/dockview-core/src/__tests__/groupview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts similarity index 100% rename from packages/dockview-core/src/__tests__/groupview/dockviewGroupPanelModel.spec.ts rename to packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts diff --git a/packages/dockview-core/src/dockview/components/tab/defaultTab.scss b/packages/dockview-core/src/dockview/components/tab/defaultTab.scss index 81208377e..d820534b8 100644 --- a/packages/dockview-core/src/dockview/components/tab/defaultTab.scss +++ b/packages/dockview-core/src/dockview/components/tab/defaultTab.scss @@ -46,5 +46,30 @@ padding: 0px 8px; flex-grow: 1; } + + .action-container { + text-align: right; + display: flex; + + .tab-list { + display: flex; + padding: 0px; + margin: 0px; + justify-content: flex-end; + + .tab-action { + padding: 4px; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + + &:hover { + border-radius: 2px; + background-color: var(--dv-icon-hover-background-color); + } + } + } + } } } diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 6e31cdce3..d6e4bf823 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -18,7 +18,7 @@ import { import { DockviewDropTargets, IWatermarkRenderer } from './types'; import { DockviewGroupPanel } from './dockviewGroupPanel'; import { IDockviewPanel } from './dockviewPanel'; -import { IGroupControlRenderer } from './options'; +import { IHeaderActionsRenderer } from './options'; export interface DndService { canDisplayOverlay( @@ -137,8 +137,8 @@ export class DockviewGroupPanelModel private watermark?: IWatermarkRenderer; private _isGroupActive = false; private _locked = false; - private _control: IGroupControlRenderer | undefined; - private _lhs: IGroupControlRenderer | undefined; + private _rightHeaderActions: IHeaderActionsRenderer | undefined; + private _leftHeaderActions: IHeaderActionsRenderer | undefined; private mostRecentlyUsed: IDockviewPanel[] = []; @@ -321,28 +321,33 @@ export class DockviewGroupPanelModel this.updateContainer(); if (this.accessor.options.createRightHeaderActionsElement) { - this._control = + this._rightHeaderActions = this.accessor.options.createRightHeaderActionsElement( this.groupPanel ); - this.addDisposables(this._control); - this._control.init({ + this.addDisposables(this._rightHeaderActions); + this._rightHeaderActions.init({ containerApi: new DockviewApi(this.accessor), api: this.groupPanel.api, }); - this.tabsContainer.setRightActionsElement(this._control.element); + this.tabsContainer.setRightActionsElement( + this._rightHeaderActions.element + ); } if (this.accessor.options.createLeftHeaderActionsElement) { - this._lhs = this.accessor.options.createLeftHeaderActionsElement( - this.groupPanel - ); - this.addDisposables(this._lhs); - this._lhs.init({ + this._leftHeaderActions = + this.accessor.options.createLeftHeaderActionsElement( + this.groupPanel + ); + this.addDisposables(this._leftHeaderActions); + this._leftHeaderActions.init({ containerApi: new DockviewApi(this.accessor), api: this.groupPanel.api, }); - this.tabsContainer.setLeftActionsElement(this._lhs.element); + this.tabsContainer.setLeftActionsElement( + this._leftHeaderActions.element + ); } } diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index bab295646..147cc5e48 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -19,7 +19,7 @@ import { Position } from '../dnd/droptarget'; import { IDockviewPanel } from './dockviewPanel'; import { FrameworkFactory } from '../panel/componentFactory'; -export interface IGroupControlRenderer extends IDisposable { +export interface IHeaderActionsRenderer extends IDisposable { readonly element: HTMLElement; init(params: { containerApi: DockviewApi; @@ -81,10 +81,10 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions { showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean; createRightHeaderActionsElement?: ( group: DockviewGroupPanel - ) => IGroupControlRenderer; + ) => IHeaderActionsRenderer; createLeftHeaderActionsElement?: ( group: DockviewGroupPanel - ) => IGroupControlRenderer; + ) => IHeaderActionsRenderer; singleTabMode?: 'fullwidth' | 'default'; parentElement?: HTMLElement; } diff --git a/packages/dockview/src/__tests__/dockview/groupControlsRenderer.spec.ts b/packages/dockview/src/__tests__/dockview/headerActionsRenderer.spec.ts similarity index 88% rename from packages/dockview/src/__tests__/dockview/groupControlsRenderer.spec.ts rename to packages/dockview/src/__tests__/dockview/headerActionsRenderer.spec.ts index dc645d007..ec6015403 100644 --- a/packages/dockview/src/__tests__/dockview/groupControlsRenderer.spec.ts +++ b/packages/dockview/src/__tests__/dockview/headerActionsRenderer.spec.ts @@ -3,9 +3,9 @@ import { DockviewGroupPanelApi, DockviewGroupPanelModel, } from 'dockview-core'; -import { ReactGroupControlsRendererPart } from '../../dockview/groupControlsRenderer'; +import { ReactHeaderActionsRendererPart } from '../../dockview/headerActionsRenderer'; -describe('groupControlsRenderer', () => { +describe('headerActionsRenderer', () => { test('#1', () => { const groupviewMock = jest.fn, []>( () => { @@ -28,7 +28,7 @@ describe('groupControlsRenderer', () => { const groupPanel = new groupPanelMock() as DockviewGroupPanel; - const cut = new ReactGroupControlsRendererPart( + const cut = new ReactHeaderActionsRendererPart( jest.fn(), { addPortal: jest.fn(), diff --git a/packages/dockview/src/__tests__/react/dockview/groupControlsRenderer.spec.ts b/packages/dockview/src/__tests__/react/dockview/groupControlsRenderer.spec.ts index d3a056372..85d55e8ae 100644 --- a/packages/dockview/src/__tests__/react/dockview/groupControlsRenderer.spec.ts +++ b/packages/dockview/src/__tests__/react/dockview/groupControlsRenderer.spec.ts @@ -3,9 +3,9 @@ import { DockviewGroupPanelApi, DockviewGroupPanelModel, } from 'dockview-core'; -import { ReactGroupControlsRendererPart } from '../../../dockview/groupControlsRenderer'; +import { ReactHeaderActionsRendererPart } from '../../../dockview/headerActionsRenderer'; -describe('groupControlsRenderer', () => { +describe('headerActionsRenderer', () => { test('#1', () => { const groupviewMock = jest.fn, []>( () => { @@ -28,7 +28,7 @@ describe('groupControlsRenderer', () => { const groupPanel = new groupPanelMock() as DockviewGroupPanel; - const cut = new ReactGroupControlsRendererPart( + const cut = new ReactHeaderActionsRendererPart( jest.fn(), { addPortal: jest.fn(), diff --git a/packages/dockview/src/dockview/dockview.tsx b/packages/dockview/src/dockview/dockview.tsx index e347a8aba..b741a61d3 100644 --- a/packages/dockview/src/dockview/dockview.tsx +++ b/packages/dockview/src/dockview/dockview.tsx @@ -4,12 +4,12 @@ import { DockviewDropEvent, DockviewDndOverlayEvent, GroupPanelFrameworkComponentFactory, - IGroupControlRenderer, DockviewPanelApi, DockviewApi, IContentRenderer, ITabRenderer, DockviewGroupPanel, + IHeaderActionsRenderer, } from 'dockview-core'; import { ReactPanelContentPart } from './reactContentPart'; import { ReactPanelHeaderPart } from './reactHeaderPart'; @@ -18,17 +18,17 @@ import { ReactPortalStore, usePortalsLifecycle } from '../react'; import { IWatermarkPanelProps, ReactWatermarkPart } from './reactWatermarkPart'; import { PanelCollection, PanelParameters } from '../types'; import { - IDockviewGroupControlProps, - ReactGroupControlsRendererPart, -} from './groupControlsRenderer'; + IDockviewHeaderActionsProps, + ReactHeaderActionsRendererPart, +} from './headerActionsRenderer'; function createGroupControlElement( - component: React.FunctionComponent | undefined, + component: React.FunctionComponent | undefined, store: ReactPortalStore -): ((groupPanel: DockviewGroupPanel) => IGroupControlRenderer) | undefined { +): ((groupPanel: DockviewGroupPanel) => IHeaderActionsRenderer) | undefined { return component ? (groupPanel: DockviewGroupPanel) => { - return new ReactGroupControlsRendererPart( + return new ReactHeaderActionsRendererPart( component, store, groupPanel @@ -65,8 +65,8 @@ export interface IDockviewReactProps { className?: string; disableAutoResizing?: boolean; defaultTabComponent?: React.FunctionComponent; - rightHeaderActionsComponent?: React.FunctionComponent; - leftHeaderActionsComponent?: React.FunctionComponent; + rightHeaderActionsComponent?: React.FunctionComponent; + leftHeaderActionsComponent?: React.FunctionComponent; singleTabMode?: 'fullwidth' | 'default'; } diff --git a/packages/dockview/src/dockview/groupControlsRenderer.ts b/packages/dockview/src/dockview/headerActionsRenderer.ts similarity index 90% rename from packages/dockview/src/dockview/groupControlsRenderer.ts rename to packages/dockview/src/dockview/headerActionsRenderer.ts index 9890d6ece..603d09061 100644 --- a/packages/dockview/src/dockview/groupControlsRenderer.ts +++ b/packages/dockview/src/dockview/headerActionsRenderer.ts @@ -10,24 +10,25 @@ import { PanelUpdateEvent, } from 'dockview-core'; -export interface IDockviewGroupControlProps { +export interface IDockviewHeaderActionsProps { api: DockviewGroupPanelApi; containerApi: DockviewApi; panels: IDockviewPanel[]; activePanel: IDockviewPanel | undefined; isGroupActive: boolean; + group: DockviewGroupPanel; } -export class ReactGroupControlsRendererPart { +export class ReactHeaderActionsRendererPart { private mutableDisposable = new DockviewMutableDisposable(); private _element: HTMLElement; - private _part?: ReactPart; + private _part?: ReactPart; get element(): HTMLElement { return this._element; } - get part(): ReactPart | undefined { + get part(): ReactPart | undefined { return this._part; } @@ -36,7 +37,7 @@ export class ReactGroupControlsRendererPart { } constructor( - private readonly component: React.FunctionComponent, + private readonly component: React.FunctionComponent, private readonly reactPortalStore: ReactPortalStore, private readonly _group: DockviewGroupPanel ) { @@ -77,6 +78,7 @@ export class ReactGroupControlsRendererPart { panels: this._group.model.panels, activePanel: this._group.model.activePanel, isGroupActive: this._group.api.isActive, + group: this._group, } ); } diff --git a/packages/dockview/src/index.ts b/packages/dockview/src/index.ts index acc7fec37..0f5e688b7 100644 --- a/packages/dockview/src/index.ts +++ b/packages/dockview/src/index.ts @@ -4,7 +4,7 @@ export * from './dockview/dockview'; export * from './dockview/defaultTab'; export * from './splitview/splitview'; export * from './gridview/gridview'; -export { IDockviewGroupControlProps } from './dockview/groupControlsRenderer'; +export { IDockviewHeaderActionsProps } from './dockview/headerActionsRenderer'; export { IWatermarkPanelProps } from './dockview/reactWatermarkPart'; export * from './paneview/paneview'; export * from './types'; diff --git a/packages/docs/docs/components/dockview.mdx b/packages/docs/docs/components/dockview.mdx index 9cc2a0cb0..11e6ba866 100644 --- a/packages/docs/docs/components/dockview.mdx +++ b/packages/docs/docs/components/dockview.mdx @@ -649,11 +649,11 @@ panel.group.locked = true; ### Group Controls Panel -`DockviewReact` accepts `leftHeaderActionsComponent` and `rightHeaderActionsComponent` which expect a React component with props `IDockviewGroupControlProps`. +`DockviewReact` accepts `leftHeaderActionsComponent` and `rightHeaderActionsComponent` which expect a React component with props `IDockviewHeaderActionsProps`. These controls are rendered of the left and right side of the space to the right of the tabs in the header bar. ```tsx -const Component: React.FunctionComponent = () => { +const Component: React.FunctionComponent = () => { return
{'...'}
; }; @@ -664,7 +664,7 @@ As a simple example the below uses the `groupControlComponent` to render a small is active and which panel is active in that group. ```tsx -const RightHeaderActionsComponent = (props: IDockviewGroupControlProps) => { +const RightHeaderActionsComponent = (props: IDockviewHeaderActionsProps) => { const isGroupActive = props.isGroupActive; const activePanel = props.activePanel; diff --git a/packages/docs/sandboxes/demo-dockview/src/app.tsx b/packages/docs/sandboxes/demo-dockview/src/app.tsx index 654cbd3f5..5ed911053 100644 --- a/packages/docs/sandboxes/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/demo-dockview/src/app.tsx @@ -4,7 +4,7 @@ import { DockviewReadyEvent, IDockviewPanelHeaderProps, IDockviewPanelProps, - IDockviewGroupControlProps, + IDockviewHeaderActionsProps, } from 'dockview'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; @@ -134,7 +134,7 @@ const groupControlsComponents = { }, }; -const GroupControls = (props: IDockviewGroupControlProps) => { +const RightControls = (props: IDockviewHeaderActionsProps) => { const Component = React.useMemo(() => { if (!props.isGroupActive || !props.activePanel) { return null; @@ -161,6 +161,36 @@ const GroupControls = (props: IDockviewGroupControlProps) => { ); }; +let counter = 0; + +const LeftControls = (props: IDockviewHeaderActionsProps) => { + const onClick = () => { + props.containerApi.addPanel({ + id: `id_${Date.now().toString()}`, + component: 'default', + title: `Tab ${counter++}`, + position: { + referenceGroup: props.group, + }, + }); + }; + + return ( +
+ +
+ ); +}; + const DockviewDemo = () => { const onReady = (event: DockviewReadyEvent) => { event.api.addPanel({ @@ -196,8 +226,6 @@ const DockviewDemo = () => { title: 'Panel 6', position: { referencePanel: 'panel_4', direction: 'below' }, }); - panel6.group.locked = true; - panel6.group.header.hidden = true; event.api.addPanel({ id: 'panel_7', component: 'default', @@ -211,8 +239,6 @@ const DockviewDemo = () => { position: { referencePanel: 'panel_7', direction: 'within' }, }); - event.api.addGroup(); - event.api.getPanel('panel_1')!.api.setActive(); }; @@ -220,7 +246,8 @@ const DockviewDemo = () => { diff --git a/packages/docs/sandboxes/groupcontrol-dockview/src/app.tsx b/packages/docs/sandboxes/groupcontrol-dockview/src/app.tsx index d49423905..059dd9448 100644 --- a/packages/docs/sandboxes/groupcontrol-dockview/src/app.tsx +++ b/packages/docs/sandboxes/groupcontrol-dockview/src/app.tsx @@ -1,7 +1,7 @@ import { DockviewReact, DockviewReadyEvent, - IDockviewGroupControlProps, + IDockviewHeaderActionsProps, IDockviewPanelProps, } from 'dockview'; import * as React from 'react'; @@ -26,7 +26,7 @@ const components = { }, }; -const RightHeaderActions = (props: IDockviewGroupControlProps) => { +const RightHeaderActions = (props: IDockviewHeaderActionsProps) => { const isGroupActive = props.isGroupActive; return ( @@ -43,7 +43,7 @@ const RightHeaderActions = (props: IDockviewGroupControlProps) => { ); }; -const LeftHeaderActions = (props: IDockviewGroupControlProps) => { +const LeftHeaderActions = (props: IDockviewHeaderActionsProps) => { const activePanel = props.activePanel; return ( From 105017245bdf36e56dda5d72b4f634c69bc8c59a Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 4 Jun 2023 15:15:41 +0100 Subject: [PATCH 03/25] feat: left header actions --- packages/docs/docs/components/dockview.mdx | 2 +- .../package.json | 4 ++-- .../public/index.html | 0 .../src/app.scss | 0 .../src/app.tsx | 0 .../src/index.tsx | 0 .../src/styles.css | 0 .../tsconfig.json | 0 .../docs/versioned_docs/version-1.7.3/components/dockview.mdx | 2 +- 9 files changed, 4 insertions(+), 4 deletions(-) rename packages/docs/sandboxes/{groupcontrol-dockview => headeractions-dockview}/package.json (94%) rename packages/docs/sandboxes/{groupcontrol-dockview => headeractions-dockview}/public/index.html (100%) rename packages/docs/sandboxes/{groupcontrol-dockview => headeractions-dockview}/src/app.scss (100%) rename packages/docs/sandboxes/{groupcontrol-dockview => headeractions-dockview}/src/app.tsx (100%) rename packages/docs/sandboxes/{groupcontrol-dockview => headeractions-dockview}/src/index.tsx (100%) rename packages/docs/sandboxes/{groupcontrol-dockview => headeractions-dockview}/src/styles.css (100%) rename packages/docs/sandboxes/{groupcontrol-dockview => headeractions-dockview}/tsconfig.json (100%) diff --git a/packages/docs/docs/components/dockview.mdx b/packages/docs/docs/components/dockview.mdx index 11e6ba866..377bb7c03 100644 --- a/packages/docs/docs/components/dockview.mdx +++ b/packages/docs/docs/components/dockview.mdx @@ -18,7 +18,7 @@ import DockviewConstraints from '@site/sandboxes/constraints-dockview/src/app'; import DndDockview from '@site/sandboxes/dnd-dockview/src/app'; import NestedDockview from '@site/sandboxes/nested-dockview/src/app'; import EventsDockview from '@site/sandboxes/events-dockview/src/app'; -import DockviewGroupControl from '@site/sandboxes/groupcontrol-dockview/src/app'; +import DockviewGroupControl from '@site/sandboxes/headeractions-dockview/src/app'; import CustomHeadersDockview from '@site/sandboxes/customheader-dockview/src/app'; import DockviewNative from '@site/sandboxes/fullwidthtab-dockview/src/app'; import DockviewNative2 from '@site/sandboxes/nativeapp-dockview/src/app'; diff --git a/packages/docs/sandboxes/groupcontrol-dockview/package.json b/packages/docs/sandboxes/headeractions-dockview/package.json similarity index 94% rename from packages/docs/sandboxes/groupcontrol-dockview/package.json rename to packages/docs/sandboxes/headeractions-dockview/package.json index 7c88c11f1..27f907944 100644 --- a/packages/docs/sandboxes/groupcontrol-dockview/package.json +++ b/packages/docs/sandboxes/headeractions-dockview/package.json @@ -1,5 +1,5 @@ { - "name": "groupcontrol-dockview", + "name": "headeractions-dockview", "description": "", "keywords": [ "dockview" @@ -29,4 +29,4 @@ "not ie <= 11", "not op_mini all" ] -} \ No newline at end of file +} diff --git a/packages/docs/sandboxes/groupcontrol-dockview/public/index.html b/packages/docs/sandboxes/headeractions-dockview/public/index.html similarity index 100% rename from packages/docs/sandboxes/groupcontrol-dockview/public/index.html rename to packages/docs/sandboxes/headeractions-dockview/public/index.html diff --git a/packages/docs/sandboxes/groupcontrol-dockview/src/app.scss b/packages/docs/sandboxes/headeractions-dockview/src/app.scss similarity index 100% rename from packages/docs/sandboxes/groupcontrol-dockview/src/app.scss rename to packages/docs/sandboxes/headeractions-dockview/src/app.scss diff --git a/packages/docs/sandboxes/groupcontrol-dockview/src/app.tsx b/packages/docs/sandboxes/headeractions-dockview/src/app.tsx similarity index 100% rename from packages/docs/sandboxes/groupcontrol-dockview/src/app.tsx rename to packages/docs/sandboxes/headeractions-dockview/src/app.tsx diff --git a/packages/docs/sandboxes/groupcontrol-dockview/src/index.tsx b/packages/docs/sandboxes/headeractions-dockview/src/index.tsx similarity index 100% rename from packages/docs/sandboxes/groupcontrol-dockview/src/index.tsx rename to packages/docs/sandboxes/headeractions-dockview/src/index.tsx diff --git a/packages/docs/sandboxes/groupcontrol-dockview/src/styles.css b/packages/docs/sandboxes/headeractions-dockview/src/styles.css similarity index 100% rename from packages/docs/sandboxes/groupcontrol-dockview/src/styles.css rename to packages/docs/sandboxes/headeractions-dockview/src/styles.css diff --git a/packages/docs/sandboxes/groupcontrol-dockview/tsconfig.json b/packages/docs/sandboxes/headeractions-dockview/tsconfig.json similarity index 100% rename from packages/docs/sandboxes/groupcontrol-dockview/tsconfig.json rename to packages/docs/sandboxes/headeractions-dockview/tsconfig.json diff --git a/packages/docs/versioned_docs/version-1.7.3/components/dockview.mdx b/packages/docs/versioned_docs/version-1.7.3/components/dockview.mdx index 835dee189..e1f7a19f2 100644 --- a/packages/docs/versioned_docs/version-1.7.3/components/dockview.mdx +++ b/packages/docs/versioned_docs/version-1.7.3/components/dockview.mdx @@ -18,7 +18,7 @@ import DockviewConstraints from '@site/sandboxes/constraints-dockview/src/app'; import DndDockview from '@site/sandboxes/dnd-dockview/src/app'; import NestedDockview from '@site/sandboxes/nested-dockview/src/app'; import EventsDockview from '@site/sandboxes/events-dockview/src/app'; -import DockviewGroupControl from '@site/sandboxes/groupcontrol-dockview/src/app'; +import DockviewGroupControl from '@site/sandboxes/headeractions-dockview/src/app'; import CustomHeadersDockview from '@site/sandboxes/customheader-dockview/src/app'; import DockviewNative from '@site/sandboxes/fullwidthtab-dockview/src/app'; import DockviewNative2 from '@site/sandboxes/nativeapp-dockview/src/app'; From 78eac85c6814aef62ed997a9242c8fab9ada20f1 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 5 Jun 2023 20:24:04 +0100 Subject: [PATCH 04/25] bug: title is deleted when updateParameters() is called with no title --- package.json | 5 +- .../__mocks__/mockDockviewPanelMode.ts | 11 +- .../src/__tests__/api/api.spec.ts | 25 ++++- .../__tests__/api/dockviewPanelApi.spec.ts | 36 ++++++- .../__tests__/dockview/dockviewPanel.spec.ts | 70 +++++++++++- .../src/__tests__/events.spec.ts | 4 + .../groupview/dockviewGroupPanelModel.spec.ts | 7 +- .../dockview-core/src/api/dockviewPanelApi.ts | 2 +- packages/dockview-core/src/api/panelApi.ts | 4 +- .../src/dockview/dockviewPanel.ts | 30 +++--- .../src/dockview/dockviewPanelModel.ts | 6 +- packages/dockview-core/src/dockview/types.ts | 12 +-- packages/dockview-core/src/events.ts | 8 +- .../src/gridview/basePanelView.ts | 13 +++ packages/dockview/jest.config.ts | 1 + .../src/__tests__/dockview/dockview.spec.tsx | 96 ++++++++++++++++- .../src/__tests__/gridview/gridview.spec.tsx | 100 +++++++++++++++++- .../src/__tests__/paneview/paneview.spec.tsx | 97 ++++++++++++++++- .../react/dockview/dockview.spec.tsx | 53 ---------- .../dockview/groupControlsRenderer.spec.ts | 58 ---------- .../react/gridview/gridview.spec.tsx | 64 ----------- .../react/paneview/paneview.spec.tsx | 52 --------- .../src/__tests__/react/react.spec.tsx | 90 ---------------- .../react/splitview/splitview.spec.tsx | 64 ----------- .../__tests__/splitview/splitview.spec.tsx | 100 +++++++++++++++++- packages/docs/docs/components/dockview.mdx | 34 ++++++ yarn.lock | 69 +++++++++++- 27 files changed, 664 insertions(+), 447 deletions(-) delete mode 100644 packages/dockview/src/__tests__/react/dockview/dockview.spec.tsx delete mode 100644 packages/dockview/src/__tests__/react/dockview/groupControlsRenderer.spec.ts delete mode 100644 packages/dockview/src/__tests__/react/gridview/gridview.spec.tsx delete mode 100644 packages/dockview/src/__tests__/react/paneview/paneview.spec.tsx delete mode 100644 packages/dockview/src/__tests__/react/react.spec.tsx delete mode 100644 packages/dockview/src/__tests__/react/splitview/splitview.spec.tsx diff --git a/package.json b/package.json index 3148b38c4..114afaa58 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "homepage": "https://github.com/mathuo/dockview#readme", "devDependencies": { "@testing-library/dom": "^8.20.0", + "@testing-library/jest-dom": "^5.16.5", "@types/jest": "^29.4.0", "@typescript-eslint/eslint-plugin": "^5.52.0", "@typescript-eslint/parser": "^5.52.0", @@ -58,8 +59,8 @@ "style-loader": "^3.3.1", "ts-jest": "^29.0.5", "ts-loader": "^9.4.2", - "tslib": "^2.5.0", "ts-node": "^10.9.1", + "tslib": "^2.5.0", "typedoc": "^0.24.7", "typescript": "^4.9.5", "webpack": "^5.75.0", @@ -67,4 +68,4 @@ "webpack-dev-server": "^4.11.1" }, "dependencies": {} -} \ No newline at end of file +} diff --git a/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelMode.ts b/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelMode.ts index 0d788620e..a1e3397dd 100644 --- a/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelMode.ts +++ b/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelMode.ts @@ -2,10 +2,10 @@ import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel'; import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; import { GroupPanelPartInitParameters, - GroupPanelUpdateEvent, IContentRenderer, ITabRenderer, } from '../../dockview/types'; +import { PanelUpdateEvent } from '../../panel/types'; export class DockviewPanelModelMock implements IDockviewPanelModel { constructor( @@ -21,7 +21,14 @@ export class DockviewPanelModelMock implements IDockviewPanelModel { // } - update(event: GroupPanelUpdateEvent): void { + updateParentGroup( + group: DockviewGroupPanel, + isPanelVisible: boolean + ): void { + // + } + + update(event: PanelUpdateEvent): void { // } diff --git a/packages/dockview-core/src/__tests__/api/api.spec.ts b/packages/dockview-core/src/__tests__/api/api.spec.ts index 10f9bc657..5b40a6aa9 100644 --- a/packages/dockview-core/src/__tests__/api/api.spec.ts +++ b/packages/dockview-core/src/__tests__/api/api.spec.ts @@ -1,4 +1,5 @@ import { PanelApiImpl } from '../../api/panelApi'; +import { IPanel } from '../../panel/types'; describe('api', () => { let api: PanelApiImpl; @@ -7,7 +8,23 @@ describe('api', () => { api = new PanelApiImpl('dummy_id'); }); - it('should update isFcoused getter', () => { + test('updateParameters', () => { + const panel = { + update: jest.fn(), + } as Partial; + + api.initialize(panel as IPanel); + + expect(panel.update).toHaveBeenCalledTimes(0); + + api.updateParameters({ keyA: 'valueA' }); + expect(panel.update).toHaveBeenCalledTimes(1); + expect(panel.update).toHaveBeenCalledWith({ + params: { keyA: 'valueA' }, + }); + }); + + test('should update isFcoused getter', () => { expect(api.isFocused).toBeFalsy(); api._onDidChangeFocus.fire({ isFocused: true }); @@ -17,7 +34,7 @@ describe('api', () => { expect(api.isFocused).toBeFalsy(); }); - it('should update isActive getter', () => { + test('should update isActive getter', () => { expect(api.isFocused).toBeFalsy(); api._onDidActiveChange.fire({ isActive: true }); @@ -27,7 +44,7 @@ describe('api', () => { expect(api.isActive).toBeFalsy(); }); - it('should update isActive getter', () => { + test('should update isActive getter', () => { expect(api.isVisible).toBeTruthy(); api._onDidVisibilityChange.fire({ isVisible: false }); @@ -37,7 +54,7 @@ describe('api', () => { expect(api.isVisible).toBeTruthy(); }); - it('should update width and height getter', () => { + test('should update width and height getter', () => { expect(api.height).toBe(0); expect(api.width).toBe(0); diff --git a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts index 243a2f4d1..1226b6cb2 100644 --- a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts +++ b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts @@ -1,4 +1,4 @@ -import { DockviewPanelApiImpl, TitleEvent } from '../../api/dockviewPanelApi'; +import { DockviewPanelApiImpl } from '../../api/dockviewPanelApi'; import { DockviewComponent } from '../../dockview/dockviewComponent'; import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel'; import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; @@ -8,6 +8,7 @@ describe('groupPanelApi', () => { const panelMock = jest.fn(() => { return { update: jest.fn(), + setTitle: jest.fn(), } as any; }); const groupMock = jest.fn(() => { @@ -20,11 +21,38 @@ describe('groupPanelApi', () => { const cut = new DockviewPanelApiImpl(panel, group); cut.setTitle('test_title'); + expect(panel.setTitle).toBeCalledTimes(1); + expect(panel.setTitle).toBeCalledWith('test_title'); + }); - expect(panel.update).toBeCalledTimes(1); - expect(panel.update).toBeCalledWith({ - params: { title: 'test_title' }, + test('updateParameters', () => { + const groupPanel: Partial = { + id: 'test_id', + update: jest.fn(), + }; + + const accessor: Partial = { + onDidAddPanel: jest.fn(), + onDidRemovePanel: jest.fn(), + options: {}, + }; + const groupViewPanel = new DockviewGroupPanel( + accessor, + '', + {} + ); + + const cut = new DockviewPanelApiImpl( + groupPanel, + groupViewPanel + ); + + cut.updateParameters({ keyA: 'valueA' }); + + expect(groupPanel.update).toHaveBeenCalledWith({ + params: { keyA: 'valueA' }, }); + expect(groupPanel.update).toHaveBeenCalledTimes(1); }); test('onDidGroupChange', () => { diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewPanel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewPanel.spec.ts index d87ac438b..c67546db8 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewPanel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewPanel.spec.ts @@ -43,7 +43,7 @@ describe('dockviewPanel', () => { expect(latestTitle).toBe('new title'); expect(cut.title).toBe('new title'); - cut.update({ params: { title: 'another title' } }); + cut.setTitle('another title'); expect(latestTitle).toBe('another title'); expect(cut.title).toBe('another title'); @@ -81,6 +81,9 @@ describe('dockviewPanel', () => { cut.setTitle('newTitle'); expect(cut.title).toBe('newTitle'); + + cut.api.setTitle('new title 2'); + expect(cut.title).toBe('new title 2'); }); test('dispose cleanup', () => { @@ -142,7 +145,7 @@ describe('dockviewPanel', () => { expect(cut.params).toEqual(undefined); - cut.update({ params: { params: { variableA: 'A', variableB: 'B' } } }); + cut.update({ params: { variableA: 'A', variableB: 'B' } }); expect(cut.params).toEqual({ variableA: 'A', variableB: 'B' }); }); @@ -181,4 +184,67 @@ describe('dockviewPanel', () => { expect(group.api.setSize).toBeCalledWith({ height: 123, width: 456 }); expect(group.api.setSize).toBeCalledTimes(1); }); + + test('updateParameter', () => { + const dockviewApiMock = jest.fn(() => { + return {} as any; + }); + const accessorMock = jest.fn(() => { + return {} as any; + }); + const groupMock = jest.fn(() => { + return {} as any; + }); + const panelModelMock = jest.fn, []>(() => { + return { + update: jest.fn(), + init: jest.fn(), + dispose: jest.fn(), + }; + }); + + const api = new dockviewApiMock(); + const accessor = new accessorMock(); + const group = new groupMock(); + const model = new panelModelMock(); + + const cut = new DockviewPanel('fake-id', accessor, api, group, model); + + cut.init({ params: { a: '1', b: '2' }, title: 'A title' }); + expect(cut.params).toEqual({ a: '1', b: '2' }); + + // update 'a' and add 'c' + cut.update({ params: { a: '-1', c: '3' } }); + expect(cut.params).toEqual({ a: '-1', b: '2', c: '3' }); + + cut.update({ params: { d: '4', e: '5', f: '6' } }); + expect(cut.params).toEqual({ + a: '-1', + b: '2', + c: '3', + d: '4', + e: '5', + f: '6', + }); + + cut.update({ + params: { + d: '', + e: null, + f: undefined, + g: '', + h: null, + i: undefined, + }, + }); + expect(cut.params).toEqual({ + a: '-1', + b: '2', + c: '3', + d: '', + e: null, + g: '', + h: null, + }); + }); }); diff --git a/packages/dockview-core/src/__tests__/events.spec.ts b/packages/dockview-core/src/__tests__/events.spec.ts index 69790cee4..6ac17cc5c 100644 --- a/packages/dockview-core/src/__tests__/events.spec.ts +++ b/packages/dockview-core/src/__tests__/events.spec.ts @@ -2,6 +2,10 @@ import { Emitter, Event } from '../events'; describe('events', () => { describe('emitter', () => { + it('debug mode is off', () => { + expect(Emitter.ENABLE_TRACKING).toBeFalsy(); + }); + it('should emit values', () => { const emitter = new Emitter(); let value: number | undefined = undefined; diff --git a/packages/dockview-core/src/__tests__/groupview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/groupview/dockviewGroupPanelModel.spec.ts index b00522d05..249167341 100644 --- a/packages/dockview-core/src/__tests__/groupview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/groupview/dockviewGroupPanelModel.spec.ts @@ -1,6 +1,5 @@ import { DockviewComponent } from '../../dockview/dockviewComponent'; import { - GroupPanelUpdateEvent, GroupviewPanelState, IGroupPanelInitParameters, GroupPanelPartInitParameters, @@ -39,7 +38,7 @@ class TestModel implements IDockviewPanelModel { this.tab = new TestContentPart(id); } - update(event: GroupPanelUpdateEvent): void { + update(event: PanelUpdateEvent): void { // } @@ -203,6 +202,10 @@ export class TestPanel implements IDockviewPanel { //noop } + setTitle(title: string): void { + // + } + update(event: PanelUpdateEvent) { //noop } diff --git a/packages/dockview-core/src/api/dockviewPanelApi.ts b/packages/dockview-core/src/api/dockviewPanelApi.ts index d42ac65a4..5c6222071 100644 --- a/packages/dockview-core/src/api/dockviewPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewPanelApi.ts @@ -89,7 +89,7 @@ export class DockviewPanelApiImpl } public setTitle(title: string): void { - this.panel.update({ params: { title } }); + this.panel.setTitle(title); } public close(): void { diff --git a/packages/dockview-core/src/api/panelApi.ts b/packages/dockview-core/src/api/panelApi.ts index 795ac1589..95d35a3b8 100644 --- a/packages/dockview-core/src/api/panelApi.ts +++ b/packages/dockview-core/src/api/panelApi.ts @@ -155,9 +155,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { this.panelUpdatesDisposable.value = this._onUpdateParameters.event( (parameters) => { panel.update({ - params: { - params: parameters, - }, + params: parameters, }); } ); diff --git a/packages/dockview-core/src/dockview/dockviewPanel.ts b/packages/dockview-core/src/dockview/dockviewPanel.ts index cc34b1545..4f67933fc 100644 --- a/packages/dockview-core/src/dockview/dockviewPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanel.ts @@ -3,14 +3,10 @@ import { DockviewPanelApi, DockviewPanelApiImpl, } from '../api/dockviewPanelApi'; -import { - GroupPanelUpdateEvent, - GroupviewPanelState, - IGroupPanelInitParameters, -} from './types'; +import { GroupviewPanelState, IGroupPanelInitParameters } from './types'; import { DockviewGroupPanel } from './dockviewGroupPanel'; import { CompositeDisposable, IDisposable } from '../lifecycle'; -import { IPanel, Parameters } from '../panel/types'; +import { IPanel, PanelUpdateEvent, Parameters } from '../panel/types'; import { IDockviewPanelModel } from './dockviewPanelModel'; import { IDockviewComponent } from './dockviewComponent'; @@ -23,7 +19,8 @@ export interface IDockviewPanel extends IDisposable, IPanel { updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void; init(params: IGroupPanelInitParameters): void; toJSON(): GroupviewPanelState; - update(event: GroupPanelUpdateEvent): void; + setTitle(title: string): void; + update(event: PanelUpdateEvent): void; } export class DockviewPanel @@ -117,19 +114,24 @@ export class DockviewPanel } } - public update(event: GroupPanelUpdateEvent): void { - const params = event.params as IGroupPanelInitParameters; - + public update(event: PanelUpdateEvent): void { + // merge the new parameters with the existing parameters this._params = { ...(this._params || {}), - ...event.params.params, + ...event.params, }; - if (params.title !== this.title) { - this._title = params.title; - this.api._onDidTitleChange.fire({ title: params.title }); + /** + * delete new keys that have a value of undefined, + * allow values of null + */ + for (const key of Object.keys(event.params)) { + if (event.params[key] === undefined) { + delete this._params[key]; + } } + // update the view with the updated props this.view.update({ params: { params: this._params, diff --git a/packages/dockview-core/src/dockview/dockviewPanelModel.ts b/packages/dockview-core/src/dockview/dockviewPanelModel.ts index b29bcd12c..44461f6a2 100644 --- a/packages/dockview-core/src/dockview/dockviewPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanelModel.ts @@ -3,19 +3,19 @@ import { GroupPanelPartInitParameters, IContentRenderer, ITabRenderer, - GroupPanelUpdateEvent, } from './types'; import { DockviewGroupPanel } from './dockviewGroupPanel'; import { IDisposable } from '../lifecycle'; import { createComponent } from '../panel/componentFactory'; import { IDockviewComponent } from './dockviewComponent'; +import { PanelUpdateEvent } from '../panel/types'; export interface IDockviewPanelModel extends IDisposable { readonly contentComponent: string; readonly tabComponent?: string; readonly content: IContentRenderer; readonly tab?: ITabRenderer; - update(event: GroupPanelUpdateEvent): void; + update(event: PanelUpdateEvent): void; layout(width: number, height: number): void; init(params: GroupPanelPartInitParameters): void; updateParentGroup(group: DockviewGroupPanel, isPanelVisible: boolean): void; @@ -80,7 +80,7 @@ export class DockviewPanelModel implements IDockviewPanelModel { this.content.layout?.(width, height); } - update(event: GroupPanelUpdateEvent): void { + update(event: PanelUpdateEvent): void { this.content.update?.(event); this.tab.update?.(event); } diff --git a/packages/dockview-core/src/dockview/types.ts b/packages/dockview-core/src/dockview/types.ts index 107da33ed..7d870746c 100644 --- a/packages/dockview-core/src/dockview/types.ts +++ b/packages/dockview-core/src/dockview/types.ts @@ -1,11 +1,6 @@ import { IDockviewComponent } from './dockviewComponent'; import { DockviewPanelApi } from '../api/dockviewPanelApi'; -import { - PanelInitParameters, - IPanel, - PanelUpdateEvent, - Parameters, -} from '../panel/types'; +import { PanelInitParameters, IPanel } from '../panel/types'; import { DockviewApi } from '../api/component.api'; import { Event } from '../events'; import { Optional } from '../types'; @@ -91,11 +86,6 @@ export interface IGroupPanelInitParameters // } -export type GroupPanelUpdateEvent = PanelUpdateEvent<{ - params?: Parameters; - title?: string; -}>; - export interface GroupviewPanelState { id: string; contentComponent?: string; diff --git a/packages/dockview-core/src/events.ts b/packages/dockview-core/src/events.ts index 13b8b3382..91f27a1b1 100644 --- a/packages/dockview-core/src/events.ts +++ b/packages/dockview-core/src/events.ts @@ -102,10 +102,10 @@ export class Emitter implements IDisposable { if (index > -1) { this._listeners.splice(index, 1); } else if (Emitter.ENABLE_TRACKING) { - console.warn( - `Listener already disposed`, - Stacktrace.create().print() - ); + // console.warn( + // `Listener already disposed`, + // Stacktrace.create().print() + // ); } }, }; diff --git a/packages/dockview-core/src/gridview/basePanelView.ts b/packages/dockview-core/src/gridview/basePanelView.ts index f61473864..4244fef36 100644 --- a/packages/dockview-core/src/gridview/basePanelView.ts +++ b/packages/dockview-core/src/gridview/basePanelView.ts @@ -104,6 +104,7 @@ export abstract class BasePanelView } update(event: PanelUpdateEvent): void { + // merge the new parameters with the existing parameters this._params = { ...this._params, params: { @@ -111,6 +112,18 @@ export abstract class BasePanelView ...event.params, }, }; + + /** + * delete new keys that have a value of undefined, + * allow values of null + */ + for (const key of Object.keys(event.params)) { + if (event.params[key] === undefined) { + delete this._params.params[key]; + } + } + + // update the view with the updated props this.part?.update({ params: this._params.params }); } diff --git a/packages/dockview/jest.config.ts b/packages/dockview/jest.config.ts index b909c68ef..5d952690f 100644 --- a/packages/dockview/jest.config.ts +++ b/packages/dockview/jest.config.ts @@ -15,6 +15,7 @@ const config: JestConfigWithTsJest = { setupFiles: [ '/packages/dockview/src/__tests__/__mocks__/resizeObserver.js', ], + setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'], coveragePathIgnorePatterns: ['/node_modules/'], modulePathIgnorePatterns: [ '/packages/dockview/src/__tests__/__mocks__', diff --git a/packages/dockview/src/__tests__/dockview/dockview.spec.tsx b/packages/dockview/src/__tests__/dockview/dockview.spec.tsx index 6fe0872a7..72e1feec5 100644 --- a/packages/dockview/src/__tests__/dockview/dockview.spec.tsx +++ b/packages/dockview/src/__tests__/dockview/dockview.spec.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { render } from '@testing-library/react'; -import { DockviewApi } from 'dockview-core'; +import { act, render, waitFor } from '@testing-library/react'; +import { DockviewApi, IDockviewPanel } from 'dockview-core'; import { IDockviewPanelProps, DockviewReact, @@ -15,7 +15,17 @@ describe('gridview react', () => { beforeEach(() => { components = { default: (props: IDockviewPanelProps) => { - return
hello world
; + return ( +
+ {Object.keys(props.params).map((key) => { + return ( +
{`key=${key},value=${props.params[key]}`}
+ ); + })} +
+ ); }, }; }); @@ -51,4 +61,84 @@ describe('gridview react', () => { expect(api!.width).toBe(650); expect(api!.height).toBe(450); }); + + test('that the component can update parameters', async () => { + let api: DockviewApi; + + const onReady = (event: DockviewReadyEvent) => { + api = event.api; + }; + + const wrapper = render( + + ); + + let panel: IDockviewPanel; + + act(() => { + panel = api!.addPanel({ + id: 'panel_1', + component: 'default', + params: { + keyA: 'valueA', + keyB: 'valueB', + }, + }); + }); + + await waitFor(() => { + expect( + wrapper.queryByText(/key=keyA,value=valueA/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + }); + + act(() => { + panel.api.updateParameters({ keyA: 'valueAA', keyC: 'valueC' }); + }); + + await waitFor(() => { + expect( + wrapper.queryByText(/key=keyA,value=valueAA/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyC,value=valueC/i) + ).toBeInTheDocument(); + }); + + act(() => { + panel.api.updateParameters({ keyC: null }); + }); + + await waitFor(() => { + expect( + wrapper.queryByText(/key=keyA,value=valueAA/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyC,value=null/i) + ).toBeInTheDocument(); + }); + + act(() => { + panel.api.updateParameters({ keyA: undefined }); + }); + + await waitFor(() => { + expect(wrapper.queryByText(/key=keyA/i)).not.toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyC,value=null/i) + ).toBeInTheDocument(); + }); + }); }); diff --git a/packages/dockview/src/__tests__/gridview/gridview.spec.tsx b/packages/dockview/src/__tests__/gridview/gridview.spec.tsx index 0881e00b4..7fdce204c 100644 --- a/packages/dockview/src/__tests__/gridview/gridview.spec.tsx +++ b/packages/dockview/src/__tests__/gridview/gridview.spec.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { render } from '@testing-library/react'; -import { GridviewApi, Orientation } from 'dockview-core'; +import { act, render, waitFor } from '@testing-library/react'; +import { GridviewApi, IGridviewPanel, Orientation } from 'dockview-core'; import { IGridviewPanelProps, GridviewReact, @@ -15,7 +15,17 @@ describe('gridview react', () => { beforeEach(() => { components = { default: (props: IGridviewPanelProps) => { - return
hello world
; + return ( +
+ {Object.keys(props.params).map((key) => { + return ( +
{`key=${key},value=${props.params[key]}`}
+ ); + })} +
+ ); }, }; }); @@ -62,4 +72,88 @@ describe('gridview react', () => { expect(api!.width).toBe(650); expect(api!.height).toBe(450); }); + + test('that the component can update parameters', async () => { + let api: GridviewApi; + + const onReady = (event: GridviewReadyEvent) => { + api = event.api; + }; + + const wrapper = render( + + ); + + let panel: IGridviewPanel; + + act(() => { + panel = api!.addPanel({ + id: 'panel_1', + component: 'default', + params: { + keyA: 'valueA', + keyB: 'valueB', + }, + }); + }); + + await waitFor(() => { + expect( + wrapper.queryByText(/key=keyA,value=valueA/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + }); + + act(() => { + panel.api.updateParameters({ keyA: 'valueAA', keyC: 'valueC' }); + }); + + await waitFor(() => { + expect( + wrapper.queryByText(/key=keyA,value=valueAA/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyC,value=valueC/i) + ).toBeInTheDocument(); + }); + + act(() => { + panel.api.updateParameters({ keyC: null }); + }); + + await waitFor(() => { + expect( + wrapper.queryByText(/key=keyA,value=valueAA/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyC,value=null/i) + ).toBeInTheDocument(); + }); + + act(() => { + panel.api.updateParameters({ keyA: undefined }); + }); + + await waitFor(() => { + expect(wrapper.queryByText(/key=keyA/i)).not.toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyC,value=null/i) + ).toBeInTheDocument(); + }); + }); }); diff --git a/packages/dockview/src/__tests__/paneview/paneview.spec.tsx b/packages/dockview/src/__tests__/paneview/paneview.spec.tsx index 830861e89..53be8476a 100644 --- a/packages/dockview/src/__tests__/paneview/paneview.spec.tsx +++ b/packages/dockview/src/__tests__/paneview/paneview.spec.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { render } from '@testing-library/react'; -import { PaneviewApi } from 'dockview-core'; +import { act, render, waitFor } from '@testing-library/react'; +import { IPaneviewPanel, PaneviewApi } from 'dockview-core'; import { IPaneviewPanelProps, PaneviewReact, @@ -15,7 +15,17 @@ describe('gridview react', () => { beforeEach(() => { components = { default: (props: IPaneviewPanelProps) => { - return
hello world
; + return ( +
+ {Object.keys(props.params).map((key) => { + return ( +
{`key=${key},value=${props.params[key]}`}
+ ); + })} +
+ ); }, }; }); @@ -49,4 +59,85 @@ describe('gridview react', () => { expect(api!.width).toBe(650); expect(api!.height).toBe(450); }); + + test('that the component can update parameters', async () => { + let api: PaneviewApi; + + const onReady = (event: PaneviewReadyEvent) => { + api = event.api; + }; + + const wrapper = render( + + ); + + let panel: IPaneviewPanel; + + act(() => { + panel = api!.addPanel({ + id: 'panel_1', + component: 'default', + title: 'Panel 1', + params: { + keyA: 'valueA', + keyB: 'valueB', + }, + }); + }); + + await waitFor(() => { + expect( + wrapper.queryByText(/key=keyA,value=valueA/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + }); + + act(() => { + panel.api.updateParameters({ keyA: 'valueAA', keyC: 'valueC' }); + }); + + await waitFor(() => { + expect( + wrapper.queryByText(/key=keyA,value=valueAA/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyC,value=valueC/i) + ).toBeInTheDocument(); + }); + + act(() => { + panel.api.updateParameters({ keyC: null }); + }); + + await waitFor(() => { + expect( + wrapper.queryByText(/key=keyA,value=valueAA/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyC,value=null/i) + ).toBeInTheDocument(); + }); + + act(() => { + panel.api.updateParameters({ keyA: undefined }); + }); + + await waitFor(() => { + expect(wrapper.queryByText(/key=keyA/i)).not.toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyC,value=null/i) + ).toBeInTheDocument(); + }); + }); }); diff --git a/packages/dockview/src/__tests__/react/dockview/dockview.spec.tsx b/packages/dockview/src/__tests__/react/dockview/dockview.spec.tsx deleted file mode 100644 index 12048cac5..000000000 --- a/packages/dockview/src/__tests__/react/dockview/dockview.spec.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import * as React from 'react'; -import { render } from '@testing-library/react'; -import { DockviewApi } from 'dockview-core'; -import { - IDockviewPanelProps, - DockviewReact, - DockviewReadyEvent, -} from '../../../dockview/dockview'; -import { PanelCollection } from '../../../types'; -import { setMockRefElement } from '../../__test_utils__/utils'; - -describe('dockview', () => { - let components: PanelCollection; - - beforeEach(() => { - components = { - default: (props: IDockviewPanelProps) => { - return
hello world
; - }, - }; - }); - - test('default', () => { - let api: DockviewApi | undefined; - - const onReady = (event: DockviewReadyEvent) => { - api = event.api; - }; - - render(); - - expect(api).toBeTruthy(); - }); - - test('is sized to container', () => { - const el = document.createElement('div') as any; - jest.spyOn(el, 'clientHeight', 'get').mockReturnValue(450); - jest.spyOn(el, 'clientWidth', 'get').mockReturnValue(650); - - setMockRefElement(el); - - let api: DockviewApi | undefined; - - const onReady = (event: DockviewReadyEvent) => { - api = event.api; - }; - - render(); - - expect(api!.width).toBe(650); - expect(api!.height).toBe(450); - }); -}); diff --git a/packages/dockview/src/__tests__/react/dockview/groupControlsRenderer.spec.ts b/packages/dockview/src/__tests__/react/dockview/groupControlsRenderer.spec.ts deleted file mode 100644 index d3a056372..000000000 --- a/packages/dockview/src/__tests__/react/dockview/groupControlsRenderer.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - DockviewGroupPanel, - DockviewGroupPanelApi, - DockviewGroupPanelModel, -} from 'dockview-core'; -import { ReactGroupControlsRendererPart } from '../../../dockview/groupControlsRenderer'; - -describe('groupControlsRenderer', () => { - test('#1', () => { - const groupviewMock = jest.fn, []>( - () => { - return { - onDidAddPanel: jest.fn(), - onDidRemovePanel: jest.fn(), - onDidActivePanelChange: jest.fn(), - }; - } - ); - - const groupview = new groupviewMock() as DockviewGroupPanelModel; - - const groupPanelMock = jest.fn, []>(() => { - return { - api: {} as DockviewGroupPanelApi as any, - model: groupview, - }; - }); - - const groupPanel = new groupPanelMock() as DockviewGroupPanel; - - const cut = new ReactGroupControlsRendererPart( - jest.fn(), - { - addPortal: jest.fn(), - }, - groupPanel - ); - - expect(cut.element.childNodes.length).toBe(0); - expect(cut.element.className).toBe('dockview-react-part'); - expect(cut.part).toBeUndefined(); - - cut.init({ - containerApi: jest.fn(), - api: { - onDidActiveChange: jest.fn(), - }, - }); - - const update = jest.fn(); - - jest.spyOn(cut.part!, 'update').mockImplementation(update); - - cut.update({ params: { valueA: 'A' } }); - - expect(update).toBeCalledWith({ valueA: 'A' }); - }); -}); diff --git a/packages/dockview/src/__tests__/react/gridview/gridview.spec.tsx b/packages/dockview/src/__tests__/react/gridview/gridview.spec.tsx deleted file mode 100644 index 5e5ff70e9..000000000 --- a/packages/dockview/src/__tests__/react/gridview/gridview.spec.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import * as React from 'react'; -import { render } from '@testing-library/react'; -import { GridviewApi, Orientation } from 'dockview-core'; -import { - IGridviewPanelProps, - GridviewReact, - GridviewReadyEvent, -} from '../../../gridview/gridview'; -import { PanelCollection } from '../../../types'; -import { setMockRefElement } from '../../__test_utils__/utils'; - -describe('gridview react', () => { - let components: PanelCollection; - - beforeEach(() => { - components = { - default: (props: IGridviewPanelProps) => { - return
hello world
; - }, - }; - }); - - test('default', () => { - let api: GridviewApi | undefined; - - const onReady = (event: GridviewReadyEvent) => { - api = event.api; - }; - - render( - - ); - - expect(api).toBeTruthy(); - }); - - test('is sized to container', () => { - setMockRefElement({ - clientHeight: 450, - clientWidth: 650, - appendChild: jest.fn(), - }); - let api: GridviewApi | undefined; - - const onReady = (event: GridviewReadyEvent) => { - api = event.api; - }; - - render( - - ); - - expect(api!.width).toBe(650); - expect(api!.height).toBe(450); - }); -}); diff --git a/packages/dockview/src/__tests__/react/paneview/paneview.spec.tsx b/packages/dockview/src/__tests__/react/paneview/paneview.spec.tsx deleted file mode 100644 index 4dc8b8198..000000000 --- a/packages/dockview/src/__tests__/react/paneview/paneview.spec.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import * as React from 'react'; -import { render } from '@testing-library/react'; -import { PaneviewApi } from 'dockview-core'; -import { - IPaneviewPanelProps, - PaneviewReact, - PaneviewReadyEvent, -} from '../../../paneview/paneview'; -import { PanelCollection } from '../../../types'; -import { setMockRefElement } from '../../__test_utils__/utils'; - -describe('gridview react', () => { - let components: PanelCollection; - - beforeEach(() => { - components = { - default: (props: IPaneviewPanelProps) => { - return
hello world
; - }, - }; - }); - - test('default', () => { - let api: PaneviewApi | undefined; - - const onReady = (event: PaneviewReadyEvent) => { - api = event.api; - }; - - render(); - - expect(api).toBeTruthy(); - }); - - test('is sized to container', () => { - setMockRefElement({ - clientHeight: 450, - clientWidth: 650, - appendChild: jest.fn(), - }); - let api: PaneviewApi | undefined; - - const onReady = (event: PaneviewReadyEvent) => { - api = event.api; - }; - - render(); - - expect(api!.width).toBe(650); - expect(api!.height).toBe(450); - }); -}); diff --git a/packages/dockview/src/__tests__/react/react.spec.tsx b/packages/dockview/src/__tests__/react/react.spec.tsx deleted file mode 100644 index b89d09d74..000000000 --- a/packages/dockview/src/__tests__/react/react.spec.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { ReactPart } from '../../react'; -import * as React from 'react'; -import { render, screen, act } from '@testing-library/react'; - -interface TestInterface { - valueA: string; - valueB: number; -} - -describe('react', () => { - describe('ReactPart', () => { - test('update underlying component via ReactPart class', () => { - let api: ReactPart; - - const onReady = (_api: ReactPart) => { - api = _api; - }; - - render(); - - expect(api!).toBeTruthy(); - - expect(screen.getByTestId('valueA').textContent).toBe('stringA'); - expect(screen.getByTestId('valueB').textContent).toBe('42'); - - act(() => { - api.update({ valueB: '32' }); - }); - - expect(screen.getByTestId('valueA').textContent).toBe('stringA'); - expect(screen.getByTestId('valueB').textContent).toBe('32'); - - act(() => { - api.update({ valueA: 'anotherStringA', valueB: '22' }); - }); - - expect(screen.getByTestId('valueA').textContent).toBe( - 'anotherStringA' - ); - expect(screen.getByTestId('valueB').textContent).toBe('22'); - }); - }); -}); - -const Component = (props: TestInterface) => { - return ( -
-
{props.valueA}
-
{props.valueB}
-
- ); -}; - -const TestWrapper = (props: { - component: React.FunctionComponent; - onReady: (api: ReactPart) => void; -}) => { - const [portal, setPortal] = React.useState([]); - const ref = React.useRef(null); - - React.useEffect(() => { - const cut = new ReactPart( - ref.current!, - { - addPortal: (portal: React.ReactPortal) => { - setPortal((_) => [..._, portal]); - - return { - dispose: () => { - setPortal((_) => _.filter((_) => _ !== portal)); - }, - }; - }, - }, - props.component, - { - valueA: 'stringA', - valueB: 42, - } - ); - - props.onReady(cut); - - return () => { - cut.dispose(); - }; - }, []); - - return
{portal}
; -}; diff --git a/packages/dockview/src/__tests__/react/splitview/splitview.spec.tsx b/packages/dockview/src/__tests__/react/splitview/splitview.spec.tsx deleted file mode 100644 index 9ffc01974..000000000 --- a/packages/dockview/src/__tests__/react/splitview/splitview.spec.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import * as React from 'react'; -import { render } from '@testing-library/react'; -import { SplitviewApi, Orientation } from 'dockview-core'; -import { - ISplitviewPanelProps, - SplitviewReact, - SplitviewReadyEvent, -} from '../../../splitview/splitview'; -import { PanelCollection } from '../../../types'; -import { setMockRefElement } from '../../__test_utils__/utils'; - -describe('splitview react', () => { - let components: PanelCollection; - - beforeEach(() => { - components = { - default: (props: ISplitviewPanelProps) => { - return
hello world
; - }, - }; - }); - - test('default', () => { - let api: SplitviewApi | undefined; - - const onReady = (event: SplitviewReadyEvent) => { - api = event.api; - }; - - render( - - ); - - expect(api).toBeTruthy(); - }); - - test('is sized to container', () => { - setMockRefElement({ - clientHeight: 450, - clientWidth: 650, - appendChild: jest.fn(), - }); - let api: SplitviewApi | undefined; - - const onReady = (event: SplitviewReadyEvent) => { - api = event.api; - }; - - render( - - ); - - expect(api!.width).toBe(650); - expect(api!.height).toBe(450); - }); -}); diff --git a/packages/dockview/src/__tests__/splitview/splitview.spec.tsx b/packages/dockview/src/__tests__/splitview/splitview.spec.tsx index 34e0027db..e85959656 100644 --- a/packages/dockview/src/__tests__/splitview/splitview.spec.tsx +++ b/packages/dockview/src/__tests__/splitview/splitview.spec.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { render } from '@testing-library/react'; -import { SplitviewApi, Orientation } from 'dockview-core'; +import { act, render, waitFor } from '@testing-library/react'; +import { SplitviewApi, Orientation, ISplitviewPanel } from 'dockview-core'; import { ISplitviewPanelProps, SplitviewReact, @@ -15,7 +15,17 @@ describe('splitview react', () => { beforeEach(() => { components = { default: (props: ISplitviewPanelProps) => { - return
hello world
; + return ( +
+ {Object.keys(props.params).map((key) => { + return ( +
{`key=${key},value=${props.params[key]}`}
+ ); + })} +
+ ); }, }; }); @@ -61,4 +71,88 @@ describe('splitview react', () => { expect(api!.width).toBe(650); expect(api!.height).toBe(450); }); + + test('that the component can update parameters', async () => { + let api: SplitviewApi; + + const onReady = (event: SplitviewReadyEvent) => { + api = event.api; + }; + + const wrapper = render( + + ); + + let panel: ISplitviewPanel; + + act(() => { + panel = api!.addPanel({ + id: 'panel_1', + component: 'default', + params: { + keyA: 'valueA', + keyB: 'valueB', + }, + }); + }); + + await waitFor(() => { + expect( + wrapper.queryByText(/key=keyA,value=valueA/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + }); + + act(() => { + panel.api.updateParameters({ keyA: 'valueAA', keyC: 'valueC' }); + }); + + await waitFor(() => { + expect( + wrapper.queryByText(/key=keyA,value=valueAA/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyC,value=valueC/i) + ).toBeInTheDocument(); + }); + + act(() => { + panel.api.updateParameters({ keyC: null }); + }); + + await waitFor(() => { + expect( + wrapper.queryByText(/key=keyA,value=valueAA/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyC,value=null/i) + ).toBeInTheDocument(); + }); + + act(() => { + panel.api.updateParameters({ keyA: undefined }); + }); + + await waitFor(() => { + expect(wrapper.queryByText(/key=keyA/i)).not.toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyB,value=valueB/i) + ).toBeInTheDocument(); + expect( + wrapper.queryByText(/key=keyC,value=null/i) + ).toBeInTheDocument(); + }); + }); }); diff --git a/packages/docs/docs/components/dockview.mdx b/packages/docs/docs/components/dockview.mdx index 835dee189..176f309db 100644 --- a/packages/docs/docs/components/dockview.mdx +++ b/packages/docs/docs/components/dockview.mdx @@ -435,6 +435,40 @@ const panel2 = api.addPanel({ }); ``` +### Update Panel + +You can programatically update the `params` passed through to the panel through the panal api using `api.updateParameters`. + +```ts +const panel = api.addPanel({ + id: 'panel_1', + component: 'default', + params: { + keyA: 'valueA', + }, +}); + +// ... + +panel.api.updateParameters({ + keyB: 'valueB', +}); + +// ... + +panel.api.updateParameters({ + keyA: 'anotherValueA', +}); +``` + +To delete a parameter you should pass a value of `undefined` for the key. + +```ts +panel.api.updateParameters({ + keyA: undefined, // this will delete 'keyA'. +}); +``` + ### Panel Rendering By default `DockviewReact` only adds to the DOM those panels that are visible, diff --git a/yarn.lock b/yarn.lock index cf1e11427..f62c689a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adobe/css-tools@^4.0.1": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.2.0.tgz#e1a84fca468f4b337816fcb7f0964beb620ba855" + integrity sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA== + "@algolia/autocomplete-core@1.7.4": version "1.7.4" resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.7.4.tgz#85ff36b2673654a393c8c505345eaedd6eaa4f70" @@ -2888,6 +2893,21 @@ lz-string "^1.4.4" pretty-format "^27.0.2" +"@testing-library/jest-dom@^5.16.5": + version "5.16.5" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" + integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA== + dependencies: + "@adobe/css-tools" "^4.0.1" + "@babel/runtime" "^7.9.2" + "@types/testing-library__jest-dom" "^5.9.1" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.5.6" + lodash "^4.17.15" + redent "^3.0.0" + "@testing-library/react@^13.4.0": version "13.4.0" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.4.0.tgz#6a31e3bf5951615593ad984e96b9e5e2d9380966" @@ -3107,6 +3127,14 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest@*": + version "29.5.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.2.tgz#86b4afc86e3a8f3005b297ed8a72494f89e6395b" + integrity sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + "@types/jest@^29.4.0": version "29.5.0" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.0.tgz#337b90bbcfe42158f39c2fb5619ad044bbb518ac" @@ -3292,6 +3320,13 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/testing-library__jest-dom@^5.9.1": + version "5.14.6" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.6.tgz#4887f6e1af11215428ab02777873bcede98a53b0" + integrity sha512-FkHXCb+ikSoUP4Y4rOslzTdX5sqYwMxfefKh1GmZ8ce1GOkEHntSp6b5cGadmNfp5e4BMEWOMx+WSKd5/MqlDA== + dependencies: + "@types/jest" "*" + "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" @@ -4659,6 +4694,14 @@ chalk@^2.0.0, chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -5488,6 +5531,11 @@ css-what@^6.0.1, css-what@^6.1.0: resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" @@ -5928,7 +5976,7 @@ docusaurus-plugin-sass@^0.2.3: dependencies: sass-loader "^10.1.1" -dom-accessibility-api@^0.5.9: +dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: version "0.5.16" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== @@ -9918,7 +9966,7 @@ markdown-escapes@^1.0.0: resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== -marked@^4.3.0: +marked@^4.2.12, marked@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== @@ -10136,6 +10184,13 @@ minimatch@^6.1.6: dependencies: brace-expansion "^2.0.1" +minimatch@^7.1.3: + version "7.4.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.6.tgz#845d6f254d8f4a5e4fd6baf44d5f10c8448365fb" + integrity sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw== + dependencies: + brace-expansion "^2.0.1" + minimatch@^7.4.1, minimatch@^7.4.2: version "7.4.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.3.tgz#012cbf110a65134bb354ae9773b55256cdb045a2" @@ -14256,6 +14311,16 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== +typedoc@^0.23.25: + version "0.23.28" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.23.28.tgz#3ce9c36ef1c273fa849d2dea18651855100d3ccd" + integrity sha512-9x1+hZWTHEQcGoP7qFmlo4unUoVJLB0H/8vfO/7wqTnZxg4kPuji9y3uRzEu0ZKez63OJAUmiGhUrtukC6Uj3w== + dependencies: + lunr "^2.3.9" + marked "^4.2.12" + minimatch "^7.1.3" + shiki "^0.14.1" + typedoc@^0.24.7: version "0.24.7" resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.24.7.tgz#7eeb272a1894b3789acc1a94b3f2ae8e7330ee39" From d9906eb802e34ecd36ab719d032e281060bb9751 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 10 Jun 2023 11:38:41 +0100 Subject: [PATCH 05/25] chore: prepare v1.7.4 --- .../docs/blog/2023-06-14-dockview-1.7.4.md | 20 +++++++++++ .../basics.mdx | 0 .../components/_category_.json | 0 .../components/dockview.mdx | 34 +++++++++++++++++++ .../components/gridview.mdx | 0 .../components/paneview.mdx | 0 .../components/splitview.mdx | 0 .../index.mdx | 0 .../theme.mdx | 0 ...ebars.json => version-1.7.4-sidebars.json} | 0 packages/docs/versions.json | 4 +-- 11 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 packages/docs/blog/2023-06-14-dockview-1.7.4.md rename packages/docs/versioned_docs/{version-1.7.3 => version-1.7.4}/basics.mdx (100%) rename packages/docs/versioned_docs/{version-1.7.3 => version-1.7.4}/components/_category_.json (100%) rename packages/docs/versioned_docs/{version-1.7.3 => version-1.7.4}/components/dockview.mdx (98%) rename packages/docs/versioned_docs/{version-1.7.3 => version-1.7.4}/components/gridview.mdx (100%) rename packages/docs/versioned_docs/{version-1.7.3 => version-1.7.4}/components/paneview.mdx (100%) rename packages/docs/versioned_docs/{version-1.7.3 => version-1.7.4}/components/splitview.mdx (100%) rename packages/docs/versioned_docs/{version-1.7.3 => version-1.7.4}/index.mdx (100%) rename packages/docs/versioned_docs/{version-1.7.3 => version-1.7.4}/theme.mdx (100%) rename packages/docs/versioned_sidebars/{version-1.7.3-sidebars.json => version-1.7.4-sidebars.json} (100%) diff --git a/packages/docs/blog/2023-06-14-dockview-1.7.4.md b/packages/docs/blog/2023-06-14-dockview-1.7.4.md new file mode 100644 index 000000000..6e8e2fe3f --- /dev/null +++ b/packages/docs/blog/2023-06-14-dockview-1.7.4.md @@ -0,0 +1,20 @@ +--- +slug: dockview-1.7.4-release +title: Dockview 1.7.4 +tags: [release] +--- + +# Release Notes + +Please reference to docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +- Improvements and tests added to the panel `api.updateParameters(...)` method [#265](https://github.com/mathuo/dockview/pull/265) + +## 🛠 Miscs + +- Fix bug associated with overidding panel titles when using `api.updateParameters(...)` [#265](https://github.com/mathuo/dockview/pull/265) +- Cleanup listeners and disposables after use [#257](https://github.com/mathuo/dockview/pull/257) + +## 🔥 Breaking changes diff --git a/packages/docs/versioned_docs/version-1.7.3/basics.mdx b/packages/docs/versioned_docs/version-1.7.4/basics.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.7.3/basics.mdx rename to packages/docs/versioned_docs/version-1.7.4/basics.mdx diff --git a/packages/docs/versioned_docs/version-1.7.3/components/_category_.json b/packages/docs/versioned_docs/version-1.7.4/components/_category_.json similarity index 100% rename from packages/docs/versioned_docs/version-1.7.3/components/_category_.json rename to packages/docs/versioned_docs/version-1.7.4/components/_category_.json diff --git a/packages/docs/versioned_docs/version-1.7.3/components/dockview.mdx b/packages/docs/versioned_docs/version-1.7.4/components/dockview.mdx similarity index 98% rename from packages/docs/versioned_docs/version-1.7.3/components/dockview.mdx rename to packages/docs/versioned_docs/version-1.7.4/components/dockview.mdx index 835dee189..176f309db 100644 --- a/packages/docs/versioned_docs/version-1.7.3/components/dockview.mdx +++ b/packages/docs/versioned_docs/version-1.7.4/components/dockview.mdx @@ -435,6 +435,40 @@ const panel2 = api.addPanel({ }); ``` +### Update Panel + +You can programatically update the `params` passed through to the panel through the panal api using `api.updateParameters`. + +```ts +const panel = api.addPanel({ + id: 'panel_1', + component: 'default', + params: { + keyA: 'valueA', + }, +}); + +// ... + +panel.api.updateParameters({ + keyB: 'valueB', +}); + +// ... + +panel.api.updateParameters({ + keyA: 'anotherValueA', +}); +``` + +To delete a parameter you should pass a value of `undefined` for the key. + +```ts +panel.api.updateParameters({ + keyA: undefined, // this will delete 'keyA'. +}); +``` + ### Panel Rendering By default `DockviewReact` only adds to the DOM those panels that are visible, diff --git a/packages/docs/versioned_docs/version-1.7.3/components/gridview.mdx b/packages/docs/versioned_docs/version-1.7.4/components/gridview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.7.3/components/gridview.mdx rename to packages/docs/versioned_docs/version-1.7.4/components/gridview.mdx diff --git a/packages/docs/versioned_docs/version-1.7.3/components/paneview.mdx b/packages/docs/versioned_docs/version-1.7.4/components/paneview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.7.3/components/paneview.mdx rename to packages/docs/versioned_docs/version-1.7.4/components/paneview.mdx diff --git a/packages/docs/versioned_docs/version-1.7.3/components/splitview.mdx b/packages/docs/versioned_docs/version-1.7.4/components/splitview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.7.3/components/splitview.mdx rename to packages/docs/versioned_docs/version-1.7.4/components/splitview.mdx diff --git a/packages/docs/versioned_docs/version-1.7.3/index.mdx b/packages/docs/versioned_docs/version-1.7.4/index.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.7.3/index.mdx rename to packages/docs/versioned_docs/version-1.7.4/index.mdx diff --git a/packages/docs/versioned_docs/version-1.7.3/theme.mdx b/packages/docs/versioned_docs/version-1.7.4/theme.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.7.3/theme.mdx rename to packages/docs/versioned_docs/version-1.7.4/theme.mdx diff --git a/packages/docs/versioned_sidebars/version-1.7.3-sidebars.json b/packages/docs/versioned_sidebars/version-1.7.4-sidebars.json similarity index 100% rename from packages/docs/versioned_sidebars/version-1.7.3-sidebars.json rename to packages/docs/versioned_sidebars/version-1.7.4-sidebars.json diff --git a/packages/docs/versions.json b/packages/docs/versions.json index 900dc7f77..a53923196 100644 --- a/packages/docs/versions.json +++ b/packages/docs/versions.json @@ -1,3 +1,3 @@ [ - "1.7.3" -] \ No newline at end of file + "1.7.4" +] From 7dde18c6369bb154425ca36e2e8cec3b8fdbfe35 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 11 Jun 2023 09:45:28 +0100 Subject: [PATCH 06/25] chore(release): publish v1.7.4 --- lerna.json | 2 +- packages/dockview-core/package.json | 2 +- packages/dockview/package.json | 4 ++-- packages/docs/package.json | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lerna.json b/lerna.json index 5d0c99866..bf55e0899 100644 --- a/lerna.json +++ b/lerna.json @@ -3,7 +3,7 @@ "packages/*" ], "useWorkspaces": true, - "version": "1.7.3", + "version": "1.7.4", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index e8bbfe657..a4d45486f 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "1.7.3", + "version": "1.7.4", "description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support", "main": "./dist/cjs/index.js", "types": "./dist/cjs/index.d.ts", diff --git a/packages/dockview/package.json b/packages/dockview/package.json index 975e2e48c..3d0acd2ff 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "1.7.3", + "version": "1.7.4", "description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support", "main": "./dist/cjs/index.js", "types": "./dist/cjs/index.d.ts", @@ -56,7 +56,7 @@ "author": "https://github.com/mathuo", "license": "MIT", "dependencies": { - "dockview-core": "^1.7.3" + "dockview-core": "^1.7.4" }, "devDependencies": { "@rollup/plugin-node-resolve": "^15.0.1", diff --git a/packages/docs/package.json b/packages/docs/package.json index a1a77a4bb..7cb47b249 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "1.7.3", + "version": "1.7.4", "private": true, "scripts": { "docusaurus": "docusaurus", @@ -22,7 +22,7 @@ "@minoru/react-dnd-treeview": "^3.4.3", "axios": "^1.3.3", "clsx": "^1.2.1", - "dockview": "^1.7.3", + "dockview": "^1.7.4", "prism-react-renderer": "^1.3.5", "react": "^18.2.0", "react-dnd": "^16.0.1", From 15393213200e9eb4b15d041803073bd0313a4bf5 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 11 Jun 2023 13:21:32 +0100 Subject: [PATCH 07/25] test: add tests to existing mouse dnd functionality --- .../src/__tests__/splitview/splitview.spec.ts | 52 ++++++++++++++++++- .../dockview-core/src/splitview/splitview.ts | 2 - 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/packages/dockview-core/src/__tests__/splitview/splitview.spec.ts b/packages/dockview-core/src/__tests__/splitview/splitview.spec.ts index 23c9b1df8..7d14653d2 100644 --- a/packages/dockview-core/src/__tests__/splitview/splitview.spec.ts +++ b/packages/dockview-core/src/__tests__/splitview/splitview.spec.ts @@ -7,7 +7,7 @@ import { Sizing, Splitview, } from '../../splitview/splitview'; - +import { fireEvent } from '@testing-library/dom'; class Testview implements IView { private _element: HTMLElement = document.createElement('div'); private _size = 0; @@ -596,4 +596,54 @@ describe('splitview', () => { expect(anyEvents).toBeFalsy(); expect(container.childNodes.length).toBe(0); }); + + test('dnd: mouse events to move sash', () => { + const splitview = new Splitview(container, { + orientation: Orientation.HORIZONTAL, + proportionalLayout: false, + }); + splitview.layout(400, 500); + + const view1 = new Testview(0, 1000); + const view2 = new Testview(0, 1000); + + splitview.addView(view1); + splitview.addView(view2); + + const sashElement = container + .getElementsByClassName('sash') + .item(0) as HTMLElement; + + // validate the expected state before drag + expect([view1.size, view2.size]).toEqual([200, 200]); + expect(sashElement).toBeTruthy(); + expect(view1.element.parentElement!.style.pointerEvents).toBe(''); + expect(view2.element.parentElement!.style.pointerEvents).toBe(''); + + // start the drag event + fireEvent.mouseDown(sashElement, { clientX: 50, clientY: 100 }); + + // during a sash drag the views should have pointer-events disabled + expect(view1.element.parentElement!.style.pointerEvents).toBe('none'); + expect(view2.element.parentElement!.style.pointerEvents).toBe('none'); + + // expect a delta move of 70 - 50 = 20 + fireEvent.mouseMove(document, { clientX: 70, clientY: 110 }); + expect([view1.size, view2.size]).toEqual([220, 180]); + + // expect a delta move of 75 - 70 = 5 + fireEvent.mouseMove(document, { clientX: 75, clientY: 110 }); + expect([view1.size, view2.size]).toEqual([225, 175]); + + // end the drag event + fireEvent.mouseUp(document); + + // expect pointer-eventes on views to be restored + expect(view1.element.parentElement!.style.pointerEvents).toBe(''); + expect(view2.element.parentElement!.style.pointerEvents).toBe(''); + + fireEvent.mouseMove(document, { clientX: 100, clientY: 100 }); + // expect no additional resizes + expect([view1.size, view2.size]).toEqual([225, 175]); + }); }); diff --git a/packages/dockview-core/src/splitview/splitview.ts b/packages/dockview-core/src/splitview/splitview.ts index 48295a8c7..fd68a4a29 100644 --- a/packages/dockview-core/src/splitview/splitview.ts +++ b/packages/dockview-core/src/splitview/splitview.ts @@ -523,14 +523,12 @@ export class Splitview { document.removeEventListener('mousemove', mousemove); document.removeEventListener('mouseup', end); - document.removeEventListener('mouseend', end); this._onDidSashEnd.fire(undefined); }; document.addEventListener('mousemove', mousemove); document.addEventListener('mouseup', end); - document.addEventListener('mouseend', end); }; sash.addEventListener('mousedown', onStart); From a39c2938f02ef54e6a55d893874104930c3f1d81 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 11 Jun 2023 14:31:28 +0100 Subject: [PATCH 08/25] test: listener utilities --- .../src/__tests__/events.spec.ts | 141 +++++++++++++++++- packages/dockview-core/src/events.ts | 4 +- 2 files changed, 142 insertions(+), 3 deletions(-) diff --git a/packages/dockview-core/src/__tests__/events.spec.ts b/packages/dockview-core/src/__tests__/events.spec.ts index 6ac17cc5c..532390a07 100644 --- a/packages/dockview-core/src/__tests__/events.spec.ts +++ b/packages/dockview-core/src/__tests__/events.spec.ts @@ -1,4 +1,9 @@ -import { Emitter, Event } from '../events'; +import { + Emitter, + Event, + addDisposableListener, + addDisposableWindowListener, +} from '../events'; describe('events', () => { describe('emitter', () => { @@ -101,4 +106,138 @@ describe('events', () => { emitter3.fire(3); expect(value).toBe(3); }); + + it('addDisposableWindowListener with capture options', () => { + const element = { + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + }; + + const handler = jest.fn(); + + const disposable = addDisposableWindowListener( + element as any, + 'mousedown', + handler, + true + ); + + expect(element.addEventListener).toBeCalledTimes(1); + expect(element.addEventListener).toHaveBeenCalledWith( + 'mousedown', + handler, + true + ); + expect(element.removeEventListener).toBeCalledTimes(0); + + disposable.dispose(); + + expect(element.addEventListener).toBeCalledTimes(1); + expect(element.removeEventListener).toBeCalledTimes(1); + expect(element.removeEventListener).toBeCalledWith( + 'mousedown', + handler, + true + ); + }); + + it('addDisposableWindowListener without capture options', () => { + const element = { + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + }; + + const handler = jest.fn(); + + const disposable = addDisposableWindowListener( + element as any, + 'mousedown', + handler + ); + + expect(element.addEventListener).toBeCalledTimes(1); + expect(element.addEventListener).toHaveBeenCalledWith( + 'mousedown', + handler, + undefined + ); + expect(element.removeEventListener).toBeCalledTimes(0); + + disposable.dispose(); + + expect(element.addEventListener).toBeCalledTimes(1); + expect(element.removeEventListener).toBeCalledTimes(1); + expect(element.removeEventListener).toBeCalledWith( + 'mousedown', + handler, + undefined + ); + }); + + it('addDisposableListener with capture options', () => { + const element = { + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + }; + + const handler = jest.fn(); + + const disposable = addDisposableListener( + element as any, + 'mousedown', + handler, + true + ); + + expect(element.addEventListener).toBeCalledTimes(1); + expect(element.addEventListener).toHaveBeenCalledWith( + 'mousedown', + handler, + true + ); + expect(element.removeEventListener).toBeCalledTimes(0); + + disposable.dispose(); + + expect(element.addEventListener).toBeCalledTimes(1); + expect(element.removeEventListener).toBeCalledTimes(1); + expect(element.removeEventListener).toBeCalledWith( + 'mousedown', + handler, + true + ); + }); + + it('addDisposableListener without capture options', () => { + const element = { + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + }; + + const handler = jest.fn(); + + const disposable = addDisposableListener( + element as any, + 'mousedown', + handler + ); + + expect(element.addEventListener).toBeCalledTimes(1); + expect(element.addEventListener).toHaveBeenCalledWith( + 'mousedown', + handler, + undefined + ); + expect(element.removeEventListener).toBeCalledTimes(0); + + disposable.dispose(); + + expect(element.addEventListener).toBeCalledTimes(1); + expect(element.removeEventListener).toBeCalledTimes(1); + expect(element.removeEventListener).toBeCalledWith( + 'mousedown', + handler, + undefined + ); + }); }); diff --git a/packages/dockview-core/src/events.ts b/packages/dockview-core/src/events.ts index 91f27a1b1..cb8d95930 100644 --- a/packages/dockview-core/src/events.ts +++ b/packages/dockview-core/src/events.ts @@ -162,7 +162,7 @@ export function addDisposableWindowListener( return { dispose: () => { - element.removeEventListener(type, listener); + element.removeEventListener(type, listener, options); }, }; } @@ -177,7 +177,7 @@ export function addDisposableListener( return { dispose: () => { - element.removeEventListener(type, listener); + element.removeEventListener(type, listener, options); }, }; } From ba3fe82c02f066415f786da50c26292b8b954bf4 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 11 Jun 2023 15:48:37 +0100 Subject: [PATCH 09/25] chore: iFrame example --- .codesandbox/ci.json | 3 +- .../__tests__/dnd/abstractDragHandler.spec.ts | 50 +++++-- .../src/dnd/abstractDragHandler.ts | 33 +++-- packages/docs/docs/components/dockview.mdx | 24 ++++ .../sandboxes/iframe-dockview/package.json | 32 +++++ .../iframe-dockview/public/index.html | 44 ++++++ .../sandboxes/iframe-dockview/src/app.tsx | 61 +++++++++ .../src/hoistedDockviewPanel.tsx | 128 ++++++++++++++++++ .../sandboxes/iframe-dockview/src/index.tsx | 20 +++ .../sandboxes/iframe-dockview/src/styles.css | 15 ++ .../sandboxes/iframe-dockview/tsconfig.json | 18 +++ .../version-1.7.4/components/dockview.mdx | 24 ++++ 12 files changed, 430 insertions(+), 22 deletions(-) create mode 100644 packages/docs/sandboxes/iframe-dockview/package.json create mode 100644 packages/docs/sandboxes/iframe-dockview/public/index.html create mode 100644 packages/docs/sandboxes/iframe-dockview/src/app.tsx create mode 100644 packages/docs/sandboxes/iframe-dockview/src/hoistedDockviewPanel.tsx create mode 100644 packages/docs/sandboxes/iframe-dockview/src/index.tsx create mode 100644 packages/docs/sandboxes/iframe-dockview/src/styles.css create mode 100644 packages/docs/sandboxes/iframe-dockview/tsconfig.json diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index 856df6dc8..f04bc5e88 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -13,6 +13,7 @@ "/packages/docs/sandboxes/externaldnd-dockview", "/packages/docs/sandboxes/fullwidthtab-dockview", "/packages/docs/sandboxes/groupcontol-dockview", + "/packages/docs/sandboxes/iframe-dockview", "/packages/docs/sandboxes/layout-dockview", "/packages/docs/sandboxes/nativeapp-dockview", "/packages/docs/sandboxes/nested-dockview", @@ -29,4 +30,4 @@ "/packages/docs/sandboxes/javascript/vanilla-dockview" ], "node": "16" -} +} \ No newline at end of file diff --git a/packages/dockview-core/src/__tests__/dnd/abstractDragHandler.spec.ts b/packages/dockview-core/src/__tests__/dnd/abstractDragHandler.spec.ts index ebb55aae7..312916ae5 100644 --- a/packages/dockview-core/src/__tests__/dnd/abstractDragHandler.spec.ts +++ b/packages/dockview-core/src/__tests__/dnd/abstractDragHandler.spec.ts @@ -20,10 +20,6 @@ describe('abstractDragHandler', () => { }, }; } - - dispose(): void { - super.dispose(); - } })(element); expect(element.classList.contains('dv-dragged')).toBeFalsy(); @@ -62,10 +58,6 @@ describe('abstractDragHandler', () => { }, }; } - - dispose(): void { - // - } })(element); expect(iframe.style.pointerEvents).toBeFalsy(); @@ -84,4 +76,46 @@ describe('abstractDragHandler', () => { handler.dispose(); }); + + test('that the disabling of pointerEvents is restored on a premature disposal of the handler', () => { + jest.useFakeTimers(); + + const element = document.createElement('div'); + const iframe = document.createElement('iframe'); + const webview = document.createElement('webview'); + const span = document.createElement('span'); + + document.body.appendChild(element); + document.body.appendChild(iframe); + document.body.appendChild(webview); + document.body.appendChild(span); + + const handler = new (class TestClass extends DragHandler { + constructor(el: HTMLElement) { + super(el); + } + + getData(): IDisposable { + return { + dispose: () => { + // / + }, + }; + } + })(element); + + expect(iframe.style.pointerEvents).toBeFalsy(); + expect(webview.style.pointerEvents).toBeFalsy(); + expect(span.style.pointerEvents).toBeFalsy(); + + fireEvent.dragStart(element); + expect(iframe.style.pointerEvents).toBe('none'); + expect(webview.style.pointerEvents).toBe('none'); + expect(span.style.pointerEvents).toBeFalsy(); + + handler.dispose(); + expect(iframe.style.pointerEvents).toBe('auto'); + expect(webview.style.pointerEvents).toBe('auto'); + expect(span.style.pointerEvents).toBeFalsy(); + }); }); diff --git a/packages/dockview-core/src/dnd/abstractDragHandler.ts b/packages/dockview-core/src/dnd/abstractDragHandler.ts index df3f49177..55e03fa42 100644 --- a/packages/dockview-core/src/dnd/abstractDragHandler.ts +++ b/packages/dockview-core/src/dnd/abstractDragHandler.ts @@ -7,17 +7,20 @@ import { } from '../lifecycle'; export abstract class DragHandler extends CompositeDisposable { - private readonly disposable = new MutableDisposable(); + private readonly dataDisposable = new MutableDisposable(); + private readonly pointerEventsDisposable = new MutableDisposable(); private readonly _onDragStart = new Emitter(); readonly onDragStart = this._onDragStart.event; - private iframes: HTMLElement[] = []; - constructor(protected readonly el: HTMLElement) { super(); - this.addDisposables(this._onDragStart); + this.addDisposables( + this._onDragStart, + this.dataDisposable, + this.pointerEventsDisposable + ); this.configure(); } @@ -28,19 +31,27 @@ export abstract class DragHandler extends CompositeDisposable { this.addDisposables( this._onDragStart, addDisposableListener(this.el, 'dragstart', (event) => { - this.iframes = [ + const iframes = [ ...getElementsByTagName('iframe'), ...getElementsByTagName('webview'), ]; - for (const iframe of this.iframes) { + this.pointerEventsDisposable.value = { + dispose: () => { + for (const iframe of iframes) { + iframe.style.pointerEvents = 'auto'; + } + }, + }; + + for (const iframe of iframes) { iframe.style.pointerEvents = 'none'; } this.el.classList.add('dv-dragged'); setTimeout(() => this.el.classList.remove('dv-dragged'), 0); - this.disposable.value = this.getData(event.dataTransfer); + this.dataDisposable.value = this.getData(event.dataTransfer); if (event.dataTransfer) { event.dataTransfer.effectAllowed = 'move'; @@ -61,12 +72,8 @@ export abstract class DragHandler extends CompositeDisposable { } }), addDisposableListener(this.el, 'dragend', () => { - for (const iframe of this.iframes) { - iframe.style.pointerEvents = 'auto'; - } - this.iframes = []; - - this.disposable.dispose(); + this.pointerEventsDisposable.dispose(); + this.dataDisposable.dispose(); }) ); } diff --git a/packages/docs/docs/components/dockview.mdx b/packages/docs/docs/components/dockview.mdx index 176f309db..576c574a3 100644 --- a/packages/docs/docs/components/dockview.mdx +++ b/packages/docs/docs/components/dockview.mdx @@ -27,6 +27,7 @@ import RenderingDockview from '@site/sandboxes/rendering-dockview/src/app'; import DockviewExternalDnd from '@site/sandboxes/externaldnd-dockview/src/app'; import DockviewResizeContainer from '@site/sandboxes/resizecontainer-dockview/src/app'; import DockviewTabheight from '@site/sandboxes/tabheight-dockview/src/app'; +import DockviewWithIFrames from '@site/sandboxes/iframe-dockview/src/app'; import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app'; import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simple-dockview/src/app'; @@ -739,6 +740,29 @@ api.group.api.setConstraints(...) +## iFrames + +iFrames required special attention because of a particular behaviour in how iFrames render: + +> Re-parenting an iFrame will reload the contents of the iFrame or the rephrase this, moving an iFrame within the DOM will cause a reload of its contents. + +You can find many examples of discussions on this. Two reputable forums for example are linked [here](https://bugzilla.mozilla.org/show_bug.cgi?id=254144) and [here](https://github.com/whatwg/html/issues/5484). + +The problem with iFrames and `dockview` is that when you hide or move a panel that panels DOM element may be moved within the DOM or removed from the DOM completely. +If your panel contains an iFrame then that iFrame will reload after being re-positioned within the DOM tree and all state in that iFrame will most likely be lost. + +`dockview` does not provide a built-in solution to this because it's too specific of a problem to include in the library. +However the below example does show an implementation of a higher-order component `HoistedDockviewPanel`that you could use to work around this problems and make iFrames behave in `dockview`. + +What the higher-order component is doing is to hoist the panels contents into a DOM element that is always present and then `position: absolute` that element to match the dimensions of it's linked panel. +The visibility of these hoisted elements is then controlled through some exposed api methods to hide elements that shouldn't be currently shown. + +You should open this example in CodeSandbox using the provided link to understand the code and make use of this implemention if required. + + + + + ## Events A simple example showing events fired by `dockviewz that can be interacted with. diff --git a/packages/docs/sandboxes/iframe-dockview/package.json b/packages/docs/sandboxes/iframe-dockview/package.json new file mode 100644 index 000000000..0a750cb5e --- /dev/null +++ b/packages/docs/sandboxes/iframe-dockview/package.json @@ -0,0 +1,32 @@ +{ + "name": "iframe-dockview", + "description": "", + "keywords": [ + "dockview" + ], + "version": "1.0.0", + "main": "src/index.tsx", + "dependencies": { + "dockview": "*", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "typescript": "^4.9.5", + "react-scripts": "*" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} \ No newline at end of file diff --git a/packages/docs/sandboxes/iframe-dockview/public/index.html b/packages/docs/sandboxes/iframe-dockview/public/index.html new file mode 100644 index 000000000..1f8a52426 --- /dev/null +++ b/packages/docs/sandboxes/iframe-dockview/public/index.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + React App + + + + +
+ + + + diff --git a/packages/docs/sandboxes/iframe-dockview/src/app.tsx b/packages/docs/sandboxes/iframe-dockview/src/app.tsx new file mode 100644 index 000000000..f52656551 --- /dev/null +++ b/packages/docs/sandboxes/iframe-dockview/src/app.tsx @@ -0,0 +1,61 @@ +import { + DockviewReact, + DockviewReadyEvent, + IDockviewPanelProps, +} from 'dockview'; +import * as React from 'react'; +import { HoistedDockviewPanel } from './hoistedDockviewPanel'; + +const components = { + iframeComponent: HoistedDockviewPanel( + (props: IDockviewPanelProps<{ color: string }>) => { + return ( +