diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index f04bc5e88..52156e5a1 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -15,6 +15,7 @@ "/packages/docs/sandboxes/groupcontol-dockview", "/packages/docs/sandboxes/iframe-dockview", "/packages/docs/sandboxes/layout-dockview", + "/packages/docs/sandboxes/lockedgroup-dockview", "/packages/docs/sandboxes/nativeapp-dockview", "/packages/docs/sandboxes/nested-dockview", "/packages/docs/sandboxes/rendering-dockview", diff --git a/lerna.json b/lerna.json index b885be29f..870ae6ce2 100644 --- a/lerna.json +++ b/lerna.json @@ -3,7 +3,7 @@ "packages/*" ], "useWorkspaces": true, - "version": "1.8.0", + "version": "1.8.2", "npmClient": "yarn", "command": { "publish": { diff --git a/package.json b/package.json index 114afaa58..c2fb363e7 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "devDependencies": { "@testing-library/dom": "^8.20.0", "@testing-library/jest-dom": "^5.16.5", + "@total-typescript/shoehorn": "^0.1.1", "@types/jest": "^29.4.0", "@typescript-eslint/eslint-plugin": "^5.52.0", "@typescript-eslint/parser": "^5.52.0", diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index f36c90e51..244e5b904 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "1.8.0", + "version": "1.8.2", "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-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 847372d8e..65ead5491 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -1,5 +1,6 @@ import { DockviewComponent } from '../../dockview/dockviewComponent'; import { + DockviewDropTargets, GroupPanelPartInitParameters, IContentRenderer, ITabRenderer, @@ -10,6 +11,8 @@ import { CompositeDisposable } from '../../lifecycle'; import { Emitter } from '../../events'; import { IDockviewPanel } from '../../dockview/dockviewPanel'; import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; +import { fireEvent } from '@testing-library/dom'; +import { getPanelData } from '../../dnd/dataTransfer'; class PanelContentPartTest implements IContentRenderer { element: HTMLElement = document.createElement('div'); @@ -3820,4 +3823,160 @@ describe('dockviewComponent', () => { expect(el.style.height).toBe('123px'); expect(el.style.width).toBe('256px'); }); + + test('that external dnd events do not trigger the top-level center dnd target unless empty', () => { + const container = document.createElement('div'); + + const showDndOverlay = jest.fn().mockReturnValue(true); + + const dockview = new DockviewComponent({ + parentElement: container, + components: { + default: PanelContentPartTest, + }, + tabComponents: { + test_tab_id: PanelTabPartTest, + }, + orientation: Orientation.HORIZONTAL, + showDndOverlay: showDndOverlay, + }); + + dockview.layout(1000, 500); + + const panel1 = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + const panel2 = dockview.addPanel({ + id: 'panel_2', + component: 'default', + position: { direction: 'right' }, + }); + + Object.defineProperty(dockview.element, 'clientWidth', { + get: () => 100, + }); + Object.defineProperty(dockview.element, 'clientHeight', { + get: () => 100, + }); + + jest.spyOn(dockview.element, 'getBoundingClientRect').mockReturnValue({ + left: 0, + top: 0, + width: 100, + height: 100, + } as any); + + // left + + const eventLeft = new KeyboardEvent('dragover'); + Object.defineProperty(eventLeft, 'clientX', { + get: () => 0, + }); + Object.defineProperty(eventLeft, 'clientY', { + get: () => 0, + }); + fireEvent(dockview.element, eventLeft); + + expect(showDndOverlay).toHaveBeenCalledWith({ + nativeEvent: eventLeft, + position: 'left', + target: DockviewDropTargets.Edge, + getData: getPanelData, + }); + expect(showDndOverlay).toBeCalledTimes(1); + + // right + + const eventRight = new KeyboardEvent('dragover'); + Object.defineProperty(eventRight, 'clientX', { + get: () => 100, + }); + Object.defineProperty(eventRight, 'clientY', { + get: () => 100, + }); + fireEvent(dockview.element, eventRight); + + expect(showDndOverlay).toHaveBeenCalledWith({ + nativeEvent: eventRight, + position: 'right', + target: DockviewDropTargets.Edge, + getData: getPanelData, + }); + expect(showDndOverlay).toBeCalledTimes(2); + + // top + + const eventTop = new KeyboardEvent('dragover'); + Object.defineProperty(eventTop, 'clientX', { + get: () => 50, + }); + Object.defineProperty(eventTop, 'clientY', { + get: () => 0, + }); + fireEvent(dockview.element, eventTop); + + expect(showDndOverlay).toHaveBeenCalledWith({ + nativeEvent: eventTop, + position: 'top', + target: DockviewDropTargets.Edge, + getData: getPanelData, + }); + expect(showDndOverlay).toBeCalledTimes(3); + + // top + + const eventBottom = new KeyboardEvent('dragover'); + Object.defineProperty(eventBottom, 'clientX', { + get: () => 50, + }); + Object.defineProperty(eventBottom, 'clientY', { + get: () => 100, + }); + fireEvent(dockview.element, eventBottom); + + expect(showDndOverlay).toHaveBeenCalledWith({ + nativeEvent: eventBottom, + position: 'bottom', + target: DockviewDropTargets.Edge, + getData: getPanelData, + }); + expect(showDndOverlay).toBeCalledTimes(4); + + // center + + const eventCenter = new KeyboardEvent('dragover'); + Object.defineProperty(eventCenter, 'clientX', { + get: () => 50, + }); + Object.defineProperty(eventCenter, 'clientY', { + get: () => 50, + }); + fireEvent(dockview.element, eventCenter); + + // expect not to be called for center + expect(showDndOverlay).toBeCalledTimes(4); + + dockview.removePanel(panel1); + dockview.removePanel(panel2); + + // center, but empty + + const eventCenter2 = new KeyboardEvent('dragover'); + Object.defineProperty(eventCenter2, 'clientX', { + get: () => 50, + }); + Object.defineProperty(eventCenter2, 'clientY', { + get: () => 50, + }); + fireEvent(dockview.element, eventCenter2); + + expect(showDndOverlay).toHaveBeenCalledWith({ + nativeEvent: eventTop, + position: 'center', + target: DockviewDropTargets.Edge, + getData: getPanelData, + }); + expect(showDndOverlay).toBeCalledTimes(5); + }); }); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts index 5d5657b80..cd1ba209b 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts @@ -1,4 +1,4 @@ -import { DockviewComponent } from '../../dockview/dockviewComponent'; +import {DockviewComponent} from '../../dockview/dockviewComponent'; import { GroupviewPanelState, IGroupPanelInitParameters, @@ -7,7 +7,7 @@ import { ITabRenderer, IWatermarkRenderer, } from '../../dockview/types'; -import { PanelUpdateEvent } from '../../panel/types'; +import { PanelUpdateEvent, Parameters } from '../../panel/types'; import { DockviewGroupPanelModel, GroupOptions, @@ -178,7 +178,7 @@ export class TestPanel implements IDockviewPanel { return this._group!; } - get params(): Record { + get params(): Parameters { return {}; } diff --git a/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts b/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts index 27e8925b1..c0d1a9754 100644 --- a/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts +++ b/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts @@ -21,7 +21,7 @@ class TestPanel implements IGridPanelView { return true; } - get params(): Record { + get params(): Parameters { return {}; } diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 6e45e87d2..168ad0b9a 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -8,6 +8,7 @@ import { AddPanelOptions, MovementOptions, } from '../dockview/options'; +import { Parameters } from '../panel/types'; import { Direction } from '../gridview/baseComponentGridview'; import { AddComponentOptions, @@ -117,7 +118,7 @@ export class SplitviewApi implements CommonApi { return this.component.layout(width, height); } - addPanel(options: AddSplitviewComponentOptions): ISplitviewPanel { + addPanel(options: AddSplitviewComponentOptions): ISplitviewPanel { return this.component.addPanel(options); } @@ -212,7 +213,7 @@ export class PaneviewApi implements CommonApi { this.component.layout(width, height); } - addPanel(options: AddPaneviewComponentOptions): IPaneviewPanel { + addPanel(options: AddPaneviewComponentOptions): IPaneviewPanel { return this.component.addPanel(options); } @@ -296,7 +297,7 @@ export class GridviewApi implements CommonApi { this.component.layout(width, height, force); } - addPanel(options: AddComponentOptions): IGridviewPanel { + addPanel(options: AddComponentOptions): IGridviewPanel { return this.component.addPanel(options); } @@ -431,7 +432,7 @@ export class DockviewApi implements CommonApi { this.component.layout(width, height, force); } - addPanel(options: AddPanelOptions): IDockviewPanel { + addPanel(options: AddPanelOptions): IDockviewPanel { return this.component.addPanel(options); } diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 9821fe1c4..7c44f894a 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -44,6 +44,7 @@ import { import { DockviewGroupPanel } from './dockviewGroupPanel'; import { DockviewPanelModel } from './dockviewPanelModel'; import { getPanelData } from '../dnd/dataTransfer'; +import { Parameters } from '../panel/types'; import { Overlay } from '../dnd/overlay'; import { toggleClass, watchElementResize } from '../dom'; import { @@ -115,7 +116,9 @@ export interface IDockviewComponent extends IBaseGrid { doSetGroupActive: (group: DockviewGroupPanel, skipFocus?: boolean) => void; removeGroup: (group: DockviewGroupPanel) => void; options: DockviewComponentOptions; - addPanel(options: AddPanelOptions): IDockviewPanel; + addPanel( + options: AddPanelOptions + ): IDockviewPanel; removePanel(panel: IDockviewPanel): void; getGroupPanel: (id: string) => IDockviewPanel | undefined; createWatermarkComponent(): IWatermarkRenderer; @@ -279,6 +282,15 @@ export class DockviewComponent } if (this.options.showDndOverlay) { + if (position === 'center' && this.gridview.length !== 0) { + /** + * for external events only show the four-corner drag overlays, disable + * the center position so that external drag events can fall through to the group + * and panel drop target handlers + */ + return false; + } + return this.options.showDndOverlay({ nativeEvent: event, position: position, @@ -696,7 +708,9 @@ export class DockviewComponent } } - addPanel(options: AddPanelOptions): DockviewPanel { + addPanel( + options: AddPanelOptions + ): DockviewPanel { if (this.panels.find((_) => _.id === options.id)) { throw new Error(`panel with id ${options.id} already exists`); } diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanel.ts index 0a1ccd52f..6823cbd4d 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanel.ts @@ -5,6 +5,7 @@ import { GroupOptions, IDockviewGroupPanelModel, IHeader, + DockviewGroupPanelLocked, } from './dockviewGroupPanelModel'; import { GridviewPanel, IGridviewPanel } from '../gridview/gridviewPanel'; import { IDockviewPanel } from '../dockview/dockviewPanel'; @@ -16,7 +17,7 @@ import { export interface IDockviewGroupPanel extends IGridviewPanel { model: IDockviewGroupPanelModel; - locked: boolean; + locked: DockviewGroupPanelLocked; readonly size: number; readonly panels: IDockviewPanel[]; readonly activePanel: IDockviewPanel | undefined; @@ -46,11 +47,11 @@ export class DockviewGroupPanel return this._model; } - get locked(): boolean { + get locked(): DockviewGroupPanelLocked { return this._model.locked; } - set locked(value: boolean) { + set locked(value: DockviewGroupPanelLocked) { this._model.locked = value; } diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 407b3cf36..d05aa7308 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -50,7 +50,7 @@ interface GroupMoveEvent { } interface CoreGroupOptions { - locked?: boolean; + locked?: DockviewGroupPanelLocked; hideHeader?: boolean; } @@ -81,6 +81,8 @@ export interface IHeader { hidden: boolean; } +export type DockviewGroupPanelLocked = boolean | 'no-drop-target'; + export interface IDockviewGroupPanelModel extends IPanel { readonly isActive: boolean; readonly size: number; @@ -93,9 +95,7 @@ export interface IDockviewGroupPanelModel extends IPanel { readonly onDidRemovePanel: Event; readonly onDidActivePanelChange: Event; readonly onMove: Event; - readonly onTabDragStart: Event; - readonly onGroupDragStart: Event; - locked: boolean; + locked: DockviewGroupPanelLocked; setActive(isActive: boolean): void; initialize(): void; // state @@ -140,7 +140,7 @@ export class DockviewGroupPanelModel private _activePanel: IDockviewPanel | undefined; private watermark?: IWatermarkRenderer; private _isGroupActive = false; - private _locked = false; + private _locked: DockviewGroupPanelLocked = false; private _isFloating = false; private _rightHeaderActions: IHeaderActionsRenderer | undefined; private _leftHeaderActions: IHeaderActionsRenderer | undefined; @@ -190,14 +190,18 @@ export class DockviewGroupPanelModel return this._activePanel; } - get locked(): boolean { + get locked(): DockviewGroupPanelLocked { return this._locked; } - set locked(value: boolean) { + set locked(value: DockviewGroupPanelLocked) { this._locked = value; - toggleClass(this.container, 'locked-groupview', value); + toggleClass( + this.container, + 'locked-groupview', + value === 'no-drop-target' || value + ); } get isActive(): boolean { @@ -272,7 +276,10 @@ export class DockviewGroupPanelModel this.dropTarget = new Droptarget(this.contentContainer.element, { acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'], canDisplayOverlay: (event, position) => { - if (this.locked && position === 'center') { + if ( + this.locked === 'no-drop-target' || + (this.locked && position === 'center') + ) { return false; } @@ -314,7 +321,7 @@ export class DockviewGroupPanelModel ); this.header.hidden = !!options.hideHeader; - this.locked = !!options.locked; + this.locked = options.locked || false; this.addDisposables( this._onTabDragStart, @@ -404,8 +411,8 @@ export class DockviewGroupPanelModel id: this.id, }; - if (this.locked) { - result.locked = true; + if (this.locked !== false) { + result.locked = this.locked; } if (this.header.hidden) { @@ -765,6 +772,10 @@ export class DockviewGroupPanelModel position: Position, index?: number ): void { + if (this.locked === 'no-drop-target') { + return; + } + const data = getPanelData(); if (data && data.viewId === this.accessor.id) { diff --git a/packages/dockview-core/src/dockview/dockviewPanel.ts b/packages/dockview-core/src/dockview/dockviewPanel.ts index c2d1b08dd..8fef552c9 100644 --- a/packages/dockview-core/src/dockview/dockviewPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanel.ts @@ -15,7 +15,7 @@ export interface IDockviewPanel extends IDisposable, IPanel { readonly group: DockviewGroupPanel; readonly api: DockviewPanelApi; readonly title: string | undefined; - readonly params: Record | undefined; + readonly params: Parameters | undefined; updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void; init(params: IGroupPanelInitParameters): void; toJSON(): GroupviewPanelState; diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index e7249d6c9..23a06e0f5 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -8,6 +8,7 @@ import { IWatermarkRenderer, DockviewDropTargets, } from './types'; +import { Parameters } from '../panel/types'; import { DockviewGroupPanel } from './dockviewGroupPanel'; import { ISplitviewStyles, Orientation } from '../splitview/splitview'; import { PanelTransfer } from '../dnd/dataTransfer'; @@ -88,10 +89,10 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions { disableFloatingGroups?: boolean; } -export interface PanelOptions { +export interface PanelOptions

{ component: string; tabComponent?: string; - params?: { [key: string]: any }; + params?: P; id: string; title?: string; } @@ -152,8 +153,8 @@ type AddPanelPositionUnion = { type AddPanelOptionsUnion = AddPanelFloatingGroupUnion | AddPanelPositionUnion; -export type AddPanelOptions = Omit< - PanelOptions, +export type AddPanelOptions

= Omit< + PanelOptions

, 'component' | 'tabComponent' > & { component: string; diff --git a/packages/dockview-core/src/gridview/basePanelView.ts b/packages/dockview-core/src/gridview/basePanelView.ts index 4244fef36..19651d5aa 100644 --- a/packages/dockview-core/src/gridview/basePanelView.ts +++ b/packages/dockview-core/src/gridview/basePanelView.ts @@ -5,13 +5,14 @@ import { PanelUpdateEvent, PanelInitParameters, IPanel, + Parameters, } from '../panel/types'; import { PanelApi, PanelApiImpl } from '../api/panelApi'; export interface BasePanelViewState { readonly id: string; readonly component: string; - readonly params?: Record; + readonly params?: Parameters; } export interface BasePanelViewExported { @@ -19,7 +20,7 @@ export interface BasePanelViewExported { readonly api: T; readonly width: number; readonly height: number; - readonly params: Record | undefined; + readonly params: Parameters | undefined; focus(): void; toJSON(): object; update(event: PanelUpdateEvent): void; @@ -50,7 +51,7 @@ export abstract class BasePanelView return this._height; } - get params(): Record | undefined { + get params(): Parameters | undefined { return this._params?.params; } diff --git a/packages/dockview-core/src/gridview/gridviewComponent.ts b/packages/dockview-core/src/gridview/gridviewComponent.ts index 12729b599..91b348fdc 100644 --- a/packages/dockview-core/src/gridview/gridviewComponent.ts +++ b/packages/dockview-core/src/gridview/gridviewComponent.ts @@ -21,7 +21,7 @@ import { GridPanelViewState, IGridviewPanel, } from './gridviewPanel'; -import { BaseComponentOptions } from '../panel/types'; +import { BaseComponentOptions, Parameters } from '../panel/types'; import { Orientation, Sizing } from '../splitview/splitview'; import { createComponent } from '../panel/componentFactory'; import { Emitter, Event } from '../events'; @@ -32,7 +32,8 @@ export interface SerializedGridviewComponent { activePanel?: string; } -export interface AddComponentOptions extends BaseComponentOptions { +export interface AddComponentOptions + extends BaseComponentOptions { minimumWidth?: number; maximumWidth?: number; minimumHeight?: number; @@ -57,7 +58,9 @@ export interface IGridviewComponent extends IBaseGrid { readonly orientation: Orientation; readonly onDidLayoutFromJSON: Event; updateOptions(options: Partial): void; - addPanel(options: AddComponentOptions): IGridviewPanel; + addPanel( + options: AddComponentOptions + ): IGridviewPanel; removePanel(panel: IGridviewPanel, sizing?: Sizing): void; focus(): void; fromJSON(serializedGridview: SerializedGridviewComponent): void; @@ -280,7 +283,9 @@ export class GridviewComponent this.doAddGroup(removedPanel, relativeLocation, options.size); } - public addPanel(options: AddComponentOptions): IGridviewPanel { + public addPanel( + options: AddComponentOptions + ): IGridviewPanel { let relativeLocation: number[] = options.location || [0]; if (options.position?.referencePanel) { diff --git a/packages/dockview-core/src/panel/types.ts b/packages/dockview-core/src/panel/types.ts index 98905e110..133e39a3b 100644 --- a/packages/dockview-core/src/panel/types.ts +++ b/packages/dockview-core/src/panel/types.ts @@ -29,10 +29,10 @@ export interface IFrameworkPart extends IDisposable { update(params: Parameters): void; } -export interface BaseComponentOptions { +export interface BaseComponentOptions { id: string; component: string; - params?: Parameters; + params?: T; snap?: boolean; priority?: LayoutPriority; size?: number; diff --git a/packages/dockview-core/src/paneview/paneviewComponent.ts b/packages/dockview-core/src/paneview/paneviewComponent.ts index 57c3b9beb..b1b3936af 100644 --- a/packages/dockview-core/src/paneview/paneviewComponent.ts +++ b/packages/dockview-core/src/paneview/paneviewComponent.ts @@ -23,6 +23,7 @@ import { DefaultHeader } from './defaultPaneviewHeader'; import { sequentialNumberGenerator } from '../math'; import { PaneTransfer } from '../dnd/dataTransfer'; import { Resizable } from '../resizable'; +import { Parameters } from '../panel/types'; const nextLayoutId = sequentialNumberGenerator(); @@ -87,13 +88,11 @@ export class PaneFramework extends DraggablePaneviewPanel { } } -export interface AddPaneviewComponentOptions { +export interface AddPaneviewComponentOptions { id: string; component: string; headerComponent?: string; - params?: { - [key: string]: any; - }; + params?: T; minimumBodySize?: number; maximumBodySize?: number; isExpanded?: boolean; @@ -115,7 +114,9 @@ export interface IPaneviewComponent extends IDisposable { readonly onDidDrop: Event; readonly onDidLayoutChange: Event; readonly onDidLayoutFromJSON: Event; - addPanel(options: AddPaneviewComponentOptions): IPaneviewPanel; + addPanel( + options: AddPaneviewComponentOptions + ): IPaneviewPanel; layout(width: number, height: number): void; toJSON(): SerializedPaneview; fromJSON(serializedPaneview: SerializedPaneview): void; @@ -233,7 +234,9 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { this._options = { ...this.options, ...options }; } - addPanel(options: AddPaneviewComponentOptions): IPaneviewPanel { + addPanel( + options: AddPaneviewComponentOptions + ): IPaneviewPanel { const body = createComponent( options.id, options.component, diff --git a/packages/dockview-core/src/splitview/splitviewComponent.ts b/packages/dockview-core/src/splitview/splitviewComponent.ts index 973a81d24..cd398d156 100644 --- a/packages/dockview-core/src/splitview/splitviewComponent.ts +++ b/packages/dockview-core/src/splitview/splitviewComponent.ts @@ -11,7 +11,7 @@ import { Splitview, } from './splitview'; import { SplitviewComponentOptions } from './options'; -import { BaseComponentOptions } from '../panel/types'; +import { BaseComponentOptions, Parameters } from '../panel/types'; import { Emitter, Event } from '../events'; import { SplitviewPanel, ISplitviewPanel } from './splitviewPanel'; import { createComponent } from '../panel/componentFactory'; @@ -39,7 +39,8 @@ export interface SerializedSplitview { views: SerializedSplitviewPanel[]; } -export interface AddSplitviewComponentOptions extends BaseComponentOptions { +export interface AddSplitviewComponentOptions + extends BaseComponentOptions { index?: number; minimumSize?: number; maximumSize?: number; @@ -62,7 +63,9 @@ export interface ISplitviewComponent extends IDisposable { readonly onDidLayoutFromJSON: Event; readonly panels: SplitviewPanel[]; updateOptions(options: Partial): void; - addPanel(options: AddSplitviewComponentOptions): ISplitviewPanel; + addPanel( + options: AddSplitviewComponentOptions + ): ISplitviewPanel; layout(width: number, height: number): void; onDidLayoutChange: Event; toJSON(): SerializedSplitview; @@ -248,7 +251,9 @@ export class SplitviewComponent return this.panels.find((view) => view.id === id); } - addPanel(options: AddSplitviewComponentOptions): SplitviewPanel { + addPanel( + options: AddSplitviewComponentOptions + ): SplitviewPanel { if (this._panels.has(options.id)) { throw new Error(`panel ${options.id} already exists`); } diff --git a/packages/dockview/package.json b/packages/dockview/package.json index a769f4503..6951af3f7 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "1.8.0", + "version": "1.8.2", "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.8.0" + "dockview-core": "^1.8.2" }, "devDependencies": { "@rollup/plugin-node-resolve": "^15.0.1", diff --git a/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx b/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx new file mode 100644 index 000000000..4d52dd18f --- /dev/null +++ b/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx @@ -0,0 +1,60 @@ +import { render, screen } from '@testing-library/react'; +import { DockviewDefaultTab } from '../../dockview/defaultTab'; +import * as React from 'react'; +import { fromPartial } from '@total-typescript/shoehorn'; +import { DockviewApi, DockviewPanelApi } from 'dockview-core'; + +describe('defaultTab', () => { + test('has close button by default', async () => { + const api = fromPartial({}); + const containerApi = fromPartial({}); + const params = {}; + + render( + + ); + + const element = await screen.getByTestId('dockview-default-tab'); + expect(element.querySelector('.dv-react-tab-close-btn')).toBeTruthy(); + }); + + test('has no close button when hideClose=true', async () => { + const api = fromPartial({}); + const containerApi = fromPartial({}); + const params = {}; + + render( + + ); + + const element = await screen.getByTestId('dockview-default-tab'); + expect(element.querySelector('.dv-react-tab-close-btn')).toBeNull(); + }); + + test('has close button when hideClose=false', async () => { + const api = fromPartial({}); + const containerApi = fromPartial({}); + const params = {}; + + render( + + ); + + const element = await screen.getByTestId('dockview-default-tab'); + expect(element.querySelector('.dv-react-tab-close-btn')).toBeTruthy(); + }); +}); diff --git a/packages/dockview/src/dockview/defaultTab.scss b/packages/dockview/src/dockview/defaultTab.scss index b40356490..3e3a89085 100644 --- a/packages/dockview/src/dockview/defaultTab.scss +++ b/packages/dockview/src/dockview/defaultTab.scss @@ -10,7 +10,7 @@ flex-grow: 1; } - .dockview-react-tab-action { + .dv-react-tab-close-btn { padding: 4px; display: flex; align-items: center; @@ -25,7 +25,7 @@ } &.inactive-tab:not(:hover) { - .dockview-react-tab-action { + .dv-react-tab-close-btn { visibility: hidden; } } diff --git a/packages/dockview/src/dockview/defaultTab.tsx b/packages/dockview/src/dockview/defaultTab.tsx index 9a35feea0..1faee461c 100644 --- a/packages/dockview/src/dockview/defaultTab.tsx +++ b/packages/dockview/src/dockview/defaultTab.tsx @@ -3,40 +3,49 @@ import * as React from 'react'; import { CloseButton } from '../svg'; export type IDockviewDefaultTabProps = IDockviewPanelHeaderProps & - React.DOMAttributes; + React.DOMAttributes & { hideClose?: boolean }; -export const DockviewDefaultTab: React.FunctionComponent = - ({ api, containerApi: _containerApi, params: _params, ...rest }) => { - const onClose = React.useCallback( - (event: React.MouseEvent) => { - event.stopPropagation(); - api.close(); - }, - [api] - ); +export const DockviewDefaultTab: React.FunctionComponent< + IDockviewDefaultTabProps +> = ({ + api, + containerApi: _containerApi, + params: _params, + hideClose, + ...rest +}) => { + const onClose = React.useCallback( + (event: React.MouseEvent) => { + event.stopPropagation(); + api.close(); + }, + [api] + ); - const onClick = React.useCallback( - (event: React.MouseEvent) => { - api.setActive(); + const onClick = React.useCallback( + (event: React.MouseEvent) => { + api.setActive(); - if (rest.onClick) { - rest.onClick(event); - } - }, - [api, rest.onClick] - ); + if (rest.onClick) { + rest.onClick(event); + } + }, + [api, rest.onClick] + ); - const iconClassname = React.useMemo(() => { - const cn = ['dockview-react-tab-action']; - return cn.join(','); - }, []); - - return ( -

- {api.title} -
+ return ( +
+ {api.title} + {!hideClose && ( +
-
- ); - }; + )} +
+ ); +}; diff --git a/packages/dockview/src/react.ts b/packages/dockview/src/react.ts index 9d78ba4c1..59299f22e 100644 --- a/packages/dockview/src/react.ts +++ b/packages/dockview/src/react.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { IFrameworkPart, IDockviewDisposable } from 'dockview-core'; +import { IFrameworkPart, IDockviewDisposable, Parameters } from 'dockview-core'; export interface ReactPortalStore { addPortal: (portal: React.ReactPortal) => IDockviewDisposable; @@ -66,7 +66,7 @@ export const ReactPartContext = React.createContext<{}>({}); export class ReactPart

implements IFrameworkPart { - private _initialProps: Record = {}; + private _initialProps: Parameters = {}; private componentInstance?: IPanelWrapperRef; private ref?: { portal: React.ReactPortal; diff --git a/packages/dockview/src/types.ts b/packages/dockview/src/types.ts index d5b7b07a3..0028f3568 100644 --- a/packages/dockview/src/types.ts +++ b/packages/dockview/src/types.ts @@ -1,9 +1,10 @@ import * as React from 'react'; +import { Parameters } from 'dockview-core'; export interface PanelCollection { [name: string]: React.FunctionComponent; } -export interface PanelParameters> { +export interface PanelParameters { params: T; } diff --git a/packages/docs/blog/2023-07-24-dockview-1.8.2.md b/packages/docs/blog/2023-07-24-dockview-1.8.2.md new file mode 100644 index 000000000..321cecd3a --- /dev/null +++ b/packages/docs/blog/2023-07-24-dockview-1.8.2.md @@ -0,0 +1,17 @@ +--- +slug: dockview-1.8.2-release +title: Dockview 1.8.2 +tags: [release] +--- + +# Release Notes + +Please reference to docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +- Fix regression related to external dnd events [#311](https://github.com/mathuo/dockview/issues/311) + +## 🔥 Breaking changes diff --git a/packages/docs/docs/components/dockview.mdx b/packages/docs/docs/components/dockview.mdx index b26720281..01cbb2717 100644 --- a/packages/docs/docs/components/dockview.mdx +++ b/packages/docs/docs/components/dockview.mdx @@ -26,6 +26,7 @@ import DockviewResizeContainer from '@site/sandboxes/resizecontainer-dockview/sr import DockviewTabheight from '@site/sandboxes/tabheight-dockview/src/app'; import DockviewWithIFrames from '@site/sandboxes/iframe-dockview/src/app'; import DockviewFloating from '@site/sandboxes/floatinggroup-dockview/src/app'; +import DockviewLockedGroup from '@site/sandboxes/lockedgroup-dockview/src/app'; import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app'; import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simple-dockview/src/app'; @@ -161,7 +162,6 @@ const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => { | group | `GroupPanel | undefined` | | isGroupActive | `boolean` | | | title | `string` | | -| suppressClosable | `boolean` | | | close | `(): void` | | | setTitle | `(title: string): void` | | @@ -776,8 +776,20 @@ You can still add groups to a locked panel programatically using the API though. ```tsx panel.group.locked = true; + +// Or + +panel.group.locked = 'no-drop-target'; ``` +Use `true` to keep drop zones top, right, bottom, left for the group. Use `no-drop-target` to disable all drop zones. For you to get a +better understanding of what this means, try and drag the panels in the example below to the locked groups. + + + ### Group Controls Panel `DockviewReact` accepts `leftHeaderActionsComponent` and `rightHeaderActionsComponent` which expect a React component with props `IDockviewHeaderActionsProps`. diff --git a/packages/docs/package.json b/packages/docs/package.json index dcde976d7..e05be24b9 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "1.8.0", + "version": "1.8.2", "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.8.0", + "dockview": "^1.8.2", "prism-react-renderer": "^1.3.5", "react": "^18.2.0", "react-dnd": "^16.0.1", diff --git a/packages/docs/sandboxes/lockedgroup-dockview/package.json b/packages/docs/sandboxes/lockedgroup-dockview/package.json new file mode 100644 index 000000000..3f7605d60 --- /dev/null +++ b/packages/docs/sandboxes/lockedgroup-dockview/package.json @@ -0,0 +1,32 @@ +{ + "name": "lockedgroup-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/lockedgroup-dockview/public/index.html b/packages/docs/sandboxes/lockedgroup-dockview/public/index.html new file mode 100644 index 000000000..1f8a52426 --- /dev/null +++ b/packages/docs/sandboxes/lockedgroup-dockview/public/index.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + React App + + + + +

+ + + + diff --git a/packages/docs/sandboxes/lockedgroup-dockview/src/app.tsx b/packages/docs/sandboxes/lockedgroup-dockview/src/app.tsx new file mode 100644 index 000000000..f63584d52 --- /dev/null +++ b/packages/docs/sandboxes/lockedgroup-dockview/src/app.tsx @@ -0,0 +1,71 @@ +import { + DockviewReact, + DockviewReadyEvent, + IDockviewPanelProps, +} from 'dockview'; +import * as React from 'react'; + +const components = { + default: (props: IDockviewPanelProps<{ title: string }>) => { + return ( +
+ {props.params.title} +
+ ); + }, +}; + +export const App: React.FC = (props: { theme?: string }) => { + const onReady = (event: DockviewReadyEvent) => { + const panel1 = event.api.addPanel({ + id: 'locked1', + component: 'default', + params: { + title: 'Locked', + }, + }); + + panel1.group.locked = true; + panel1.group.header.hidden = true; + + event.api.addPanel({ + id: 'Drag me', + component: 'default', + params: { + title: '', + }, + position: { referencePanel: 'locked1', direction: 'right' }, + }); + + event.api.addPanel({ + id: 'Drag me too', + component: 'default', + params: { + title: '', + }, + position: { referencePanel: 'Drag me', direction: 'right' }, + }); + + const panel3 = event.api.addPanel({ + id: 'locked2', + component: 'default', + params: { + title: 'Locked with no drop target', + }, + position: { referencePanel: 'Drag me too', direction: 'right' }, + }); + + panel3.group.locked = 'no-drop-target'; + panel3.group.header.hidden = true; + }; + + return ( + + ); +}; + +export default App; diff --git a/packages/docs/sandboxes/lockedgroup-dockview/src/index.tsx b/packages/docs/sandboxes/lockedgroup-dockview/src/index.tsx new file mode 100644 index 000000000..2fe1be232 --- /dev/null +++ b/packages/docs/sandboxes/lockedgroup-dockview/src/index.tsx @@ -0,0 +1,20 @@ +import { StrictMode } from 'react'; +import * as ReactDOMClient from 'react-dom/client'; +import './styles.css'; +import 'dockview/dist/styles/dockview.css'; + +import App from './app'; + +const rootElement = document.getElementById('root'); + +if (rootElement) { + const root = ReactDOMClient.createRoot(rootElement); + + root.render( + +
+ +
+
+ ); +} diff --git a/packages/docs/sandboxes/lockedgroup-dockview/src/styles.css b/packages/docs/sandboxes/lockedgroup-dockview/src/styles.css new file mode 100644 index 000000000..92b6a1b36 --- /dev/null +++ b/packages/docs/sandboxes/lockedgroup-dockview/src/styles.css @@ -0,0 +1,16 @@ +body { + margin: 0px; + color: white; + font-family: sans-serif; + text-align: center; +} + +#root { + height: 100vh; + width: 100vw; +} + +.app { + height: 100%; + +} diff --git a/packages/docs/sandboxes/lockedgroup-dockview/tsconfig.json b/packages/docs/sandboxes/lockedgroup-dockview/tsconfig.json new file mode 100644 index 000000000..cdc4fb5f5 --- /dev/null +++ b/packages/docs/sandboxes/lockedgroup-dockview/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "outDir": "build/dist", + "module": "esnext", + "target": "es5", + "lib": ["es6", "dom"], + "sourceMap": true, + "allowJs": true, + "jsx": "react-jsx", + "moduleResolution": "node", + "rootDir": "src", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "strictNullChecks": true + } +} diff --git a/packages/docs/versioned_docs/version-1.8.0/components/_category_.json b/packages/docs/versioned_docs/version-1.8.2/components/_category_.json similarity index 100% rename from packages/docs/versioned_docs/version-1.8.0/components/_category_.json rename to packages/docs/versioned_docs/version-1.8.2/components/_category_.json diff --git a/packages/docs/versioned_docs/version-1.8.0/components/dockview.mdx b/packages/docs/versioned_docs/version-1.8.2/components/dockview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.0/components/dockview.mdx rename to packages/docs/versioned_docs/version-1.8.2/components/dockview.mdx diff --git a/packages/docs/versioned_docs/version-1.8.0/components/gridview.mdx b/packages/docs/versioned_docs/version-1.8.2/components/gridview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.0/components/gridview.mdx rename to packages/docs/versioned_docs/version-1.8.2/components/gridview.mdx diff --git a/packages/docs/versioned_docs/version-1.8.0/components/paneview.mdx b/packages/docs/versioned_docs/version-1.8.2/components/paneview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.0/components/paneview.mdx rename to packages/docs/versioned_docs/version-1.8.2/components/paneview.mdx diff --git a/packages/docs/versioned_docs/version-1.8.0/components/splitview.mdx b/packages/docs/versioned_docs/version-1.8.2/components/splitview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.0/components/splitview.mdx rename to packages/docs/versioned_docs/version-1.8.2/components/splitview.mdx diff --git a/packages/docs/versioned_docs/version-1.8.0/contributing.mdx b/packages/docs/versioned_docs/version-1.8.2/contributing.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.0/contributing.mdx rename to packages/docs/versioned_docs/version-1.8.2/contributing.mdx diff --git a/packages/docs/versioned_docs/version-1.8.0/index.mdx b/packages/docs/versioned_docs/version-1.8.2/index.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.0/index.mdx rename to packages/docs/versioned_docs/version-1.8.2/index.mdx diff --git a/packages/docs/versioned_docs/version-1.8.0/theme.mdx b/packages/docs/versioned_docs/version-1.8.2/theme.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.0/theme.mdx rename to packages/docs/versioned_docs/version-1.8.2/theme.mdx diff --git a/packages/docs/versioned_sidebars/version-1.8.0-sidebars.json b/packages/docs/versioned_sidebars/version-1.8.2-sidebars.json similarity index 100% rename from packages/docs/versioned_sidebars/version-1.8.0-sidebars.json rename to packages/docs/versioned_sidebars/version-1.8.2-sidebars.json diff --git a/packages/docs/versions.json b/packages/docs/versions.json index 121848cb7..cc0c8b8a8 100644 --- a/packages/docs/versions.json +++ b/packages/docs/versions.json @@ -1,4 +1,4 @@ [ - "1.8.0", + "1.8.2", "1.7.6" -] +] \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 9863d207e..70562dff6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2961,6 +2961,11 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== +"@total-typescript/shoehorn@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@total-typescript/shoehorn/-/shoehorn-0.1.1.tgz#72d3ba9364faa4f6b8e66c57b7a9094457e3652b" + integrity sha512-XSPcazQsC2Cr7eCiAI+M2bTmMziBvFWYTYMgUDKLbU6i+7m3I2BF5gXF5vKDO8577fONs9CvmTvVa7+nMHMfxg== + "@trysound/sax@0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"