Merge branch 'master' of https://github.com/mathuo/dockview into 299-access-to-datatransfer-object-on-drag-events

This commit is contained in:
mathuo 2023-08-27 21:51:31 +01:00
commit 6c2d511827
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
44 changed files with 602 additions and 94 deletions

View File

@ -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",

View File

@ -3,7 +3,7 @@
"packages/*"
],
"useWorkspaces": true,
"version": "1.8.0",
"version": "1.8.2",
"npmClient": "yarn",
"command": {
"publish": {

View File

@ -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",

View File

@ -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",

View File

@ -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);
});
});

View File

@ -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<string, any> {
get params(): Parameters {
return {};
}

View File

@ -21,7 +21,7 @@ class TestPanel implements IGridPanelView {
return true;
}
get params(): Record<string, any> {
get params(): Parameters {
return {};
}

View File

@ -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<SerializedSplitview> {
return this.component.layout(width, height);
}
addPanel(options: AddSplitviewComponentOptions): ISplitviewPanel {
addPanel<T extends object = Parameters>(options: AddSplitviewComponentOptions<T>): ISplitviewPanel {
return this.component.addPanel(options);
}
@ -212,7 +213,7 @@ export class PaneviewApi implements CommonApi<SerializedPaneview> {
this.component.layout(width, height);
}
addPanel(options: AddPaneviewComponentOptions): IPaneviewPanel {
addPanel<T extends object = Parameters>(options: AddPaneviewComponentOptions<T>): IPaneviewPanel {
return this.component.addPanel(options);
}
@ -296,7 +297,7 @@ export class GridviewApi implements CommonApi<SerializedGridviewComponent> {
this.component.layout(width, height, force);
}
addPanel(options: AddComponentOptions): IGridviewPanel {
addPanel<T extends object = Parameters>(options: AddComponentOptions<T>): IGridviewPanel {
return this.component.addPanel(options);
}
@ -431,7 +432,7 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
this.component.layout(width, height, force);
}
addPanel(options: AddPanelOptions): IDockviewPanel {
addPanel<T extends object = Parameters>(options: AddPanelOptions<T>): IDockviewPanel {
return this.component.addPanel(options);
}

View File

@ -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<DockviewGroupPanel> {
doSetGroupActive: (group: DockviewGroupPanel, skipFocus?: boolean) => void;
removeGroup: (group: DockviewGroupPanel) => void;
options: DockviewComponentOptions;
addPanel(options: AddPanelOptions): IDockviewPanel;
addPanel<T extends object = Parameters>(
options: AddPanelOptions<T>
): 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<T extends object = Parameters>(
options: AddPanelOptions<T>
): DockviewPanel {
if (this.panels.find((_) => _.id === options.id)) {
throw new Error(`panel with id ${options.id} already exists`);
}

View File

@ -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<DockviewGroupPanelApi> {
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;
}

View File

@ -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<GroupviewChangeEvent>;
readonly onDidActivePanelChange: Event<GroupviewChangeEvent>;
readonly onMove: Event<GroupMoveEvent>;
readonly onTabDragStart: Event<TabDragEvent>;
readonly onGroupDragStart: Event<GroupDragEvent>;
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) {

View File

@ -15,7 +15,7 @@ export interface IDockviewPanel extends IDisposable, IPanel {
readonly group: DockviewGroupPanel;
readonly api: DockviewPanelApi;
readonly title: string | undefined;
readonly params: Record<string, any> | undefined;
readonly params: Parameters | undefined;
updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void;
init(params: IGroupPanelInitParameters): void;
toJSON(): GroupviewPanelState;

View File

@ -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<P extends object = Parameters> {
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<P extends object = Parameters> = Omit<
PanelOptions<P>,
'component' | 'tabComponent'
> & {
component: string;

View File

@ -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<string, any>;
readonly params?: Parameters;
}
export interface BasePanelViewExported<T extends PanelApi> {
@ -19,7 +20,7 @@ export interface BasePanelViewExported<T extends PanelApi> {
readonly api: T;
readonly width: number;
readonly height: number;
readonly params: Record<string, any> | undefined;
readonly params: Parameters | undefined;
focus(): void;
toJSON(): object;
update(event: PanelUpdateEvent): void;
@ -50,7 +51,7 @@ export abstract class BasePanelView<T extends PanelApiImpl>
return this._height;
}
get params(): Record<string, any> | undefined {
get params(): Parameters | undefined {
return this._params?.params;
}

View File

@ -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<T extends object = Parameters>
extends BaseComponentOptions<T> {
minimumWidth?: number;
maximumWidth?: number;
minimumHeight?: number;
@ -57,7 +58,9 @@ export interface IGridviewComponent extends IBaseGrid<GridviewPanel> {
readonly orientation: Orientation;
readonly onDidLayoutFromJSON: Event<void>;
updateOptions(options: Partial<GridviewComponentUpdateOptions>): void;
addPanel(options: AddComponentOptions): IGridviewPanel;
addPanel<T extends object = Parameters>(
options: AddComponentOptions<T>
): 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<T extends object = Parameters>(
options: AddComponentOptions<T>
): IGridviewPanel {
let relativeLocation: number[] = options.location || [0];
if (options.position?.referencePanel) {

View File

@ -29,10 +29,10 @@ export interface IFrameworkPart extends IDisposable {
update(params: Parameters): void;
}
export interface BaseComponentOptions {
export interface BaseComponentOptions<T extends object = Parameters> {
id: string;
component: string;
params?: Parameters;
params?: T;
snap?: boolean;
priority?: LayoutPriority;
size?: number;

View File

@ -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<T extends object = Parameters> {
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<PaneviewDropEvent>;
readonly onDidLayoutChange: Event<void>;
readonly onDidLayoutFromJSON: Event<void>;
addPanel(options: AddPaneviewComponentOptions): IPaneviewPanel;
addPanel<T extends object = Parameters>(
options: AddPaneviewComponentOptions<T>
): 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<T extends object = Parameters>(
options: AddPaneviewComponentOptions<T>
): IPaneviewPanel {
const body = createComponent(
options.id,
options.component,

View File

@ -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<T extends Parameters = Parameters>
extends BaseComponentOptions<T> {
index?: number;
minimumSize?: number;
maximumSize?: number;
@ -62,7 +63,9 @@ export interface ISplitviewComponent extends IDisposable {
readonly onDidLayoutFromJSON: Event<void>;
readonly panels: SplitviewPanel[];
updateOptions(options: Partial<SplitviewComponentUpdateOptions>): void;
addPanel(options: AddSplitviewComponentOptions): ISplitviewPanel;
addPanel<T extends object = Parameters>(
options: AddSplitviewComponentOptions<T>
): ISplitviewPanel;
layout(width: number, height: number): void;
onDidLayoutChange: Event<void>;
toJSON(): SerializedSplitview;
@ -248,7 +251,9 @@ export class SplitviewComponent
return this.panels.find((view) => view.id === id);
}
addPanel(options: AddSplitviewComponentOptions): SplitviewPanel {
addPanel<T extends object = Parameters>(
options: AddSplitviewComponentOptions<T>
): SplitviewPanel {
if (this._panels.has(options.id)) {
throw new Error(`panel ${options.id} already exists`);
}

View File

@ -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",

View File

@ -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<DockviewPanelApi>({});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
render(
<DockviewDefaultTab
api={api}
containerApi={containerApi}
params={params}
/>
);
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<DockviewPanelApi>({});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
render(
<DockviewDefaultTab
api={api}
containerApi={containerApi}
params={params}
hideClose={true}
/>
);
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<DockviewPanelApi>({});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
render(
<DockviewDefaultTab
api={api}
containerApi={containerApi}
params={params}
hideClose={false}
/>
);
const element = await screen.getByTestId('dockview-default-tab');
expect(element.querySelector('.dv-react-tab-close-btn')).toBeTruthy();
});
});

View File

@ -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;
}
}

View File

@ -3,40 +3,49 @@ import * as React from 'react';
import { CloseButton } from '../svg';
export type IDockviewDefaultTabProps = IDockviewPanelHeaderProps &
React.DOMAttributes<HTMLDivElement>;
React.DOMAttributes<HTMLDivElement> & { hideClose?: boolean };
export const DockviewDefaultTab: React.FunctionComponent<IDockviewDefaultTabProps> =
({ api, containerApi: _containerApi, params: _params, ...rest }) => {
const onClose = React.useCallback(
(event: React.MouseEvent<HTMLSpanElement>) => {
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<HTMLSpanElement>) => {
event.stopPropagation();
api.close();
},
[api]
);
const onClick = React.useCallback(
(event: React.MouseEvent<HTMLDivElement>) => {
api.setActive();
const onClick = React.useCallback(
(event: React.MouseEvent<HTMLDivElement>) => {
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 (
<div {...rest} onClick={onClick} className="dockview-react-tab">
<span className="dockview-react-tab-title">{api.title}</span>
<div className={iconClassname} onClick={onClose}>
return (
<div
data-testid="dockview-default-tab"
{...rest}
onClick={onClick}
className="dockview-react-tab"
>
<span className="dockview-react-tab-title">{api.title}</span>
{!hideClose && (
<div className="dv-react-tab-close-btn" onClick={onClose}>
<CloseButton />
</div>
</div>
);
};
)}
</div>
);
};

View File

@ -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<P extends object, C extends object = {}>
implements IFrameworkPart
{
private _initialProps: Record<string, any> = {};
private _initialProps: Parameters = {};
private componentInstance?: IPanelWrapperRef;
private ref?: {
portal: React.ReactPortal;

View File

@ -1,9 +1,10 @@
import * as React from 'react';
import { Parameters } from 'dockview-core';
export interface PanelCollection<T extends object> {
[name: string]: React.FunctionComponent<T>;
}
export interface PanelParameters<T extends {} = Record<string, any>> {
export interface PanelParameters<T extends {} = Parameters> {
params: T;
}

View File

@ -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

View File

@ -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.
<MultiFrameworkContainer
sandboxId="lockedgroup-dockview"
react={DockviewLockedGroup}
/>
### Group Controls Panel
`DockviewReact` accepts `leftHeaderActionsComponent` and `rightHeaderActionsComponent` which expect a React component with props `IDockviewHeaderActionsProps`.

View File

@ -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",

View File

@ -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"
]
}

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -0,0 +1,71 @@
import {
DockviewReact,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview';
import * as React from 'react';
const components = {
default: (props: IDockviewPanelProps<{ title: string }>) => {
return (
<div style={{ padding: '20px', color: 'white' }}>
{props.params.title}
</div>
);
},
};
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 (
<DockviewReact
components={components}
onReady={onReady}
className={props.theme || 'dockview-theme-abyss'}
/>
);
};
export default App;

View File

@ -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(
<StrictMode>
<div className="app">
<App />
</div>
</StrictMode>
);
}

View File

@ -0,0 +1,16 @@
body {
margin: 0px;
color: white;
font-family: sans-serif;
text-align: center;
}
#root {
height: 100vh;
width: 100vw;
}
.app {
height: 100%;
}

View File

@ -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
}
}

View File

@ -1,4 +1,4 @@
[
"1.8.0",
"1.8.2",
"1.7.6"
]
]

View File

@ -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"