Merge pull request #628 from mathuo/544-floatinggroups-support-for-css-absolute-box-attributes-bottom-and-right

544 floatinggroups support for css absolute box attributes bottom and right
This commit is contained in:
mathuo 2024-07-03 22:18:34 +01:00 committed by GitHub
commit f20b5285da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1545 additions and 228 deletions

View File

@ -1,7 +1,34 @@
import { Overlay } from '../../dnd/overlay';
const mockGetBoundingClientRect = ({
left,
top,
height,
width,
}: {
left: number;
top: number;
height: number;
width: number;
}) => {
const result = {
left,
top,
height,
width,
right: left + width,
bottom: top + height,
x: left,
y: top,
};
return {
...result,
toJSON: () => result,
};
};
describe('overlay', () => {
test('toJSON', () => {
test('toJSON, top left', () => {
const container = document.createElement('div');
const content = document.createElement('div');
@ -23,14 +50,26 @@ describe('overlay', () => {
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return { left: 80, top: 100, width: 40, height: 50 } as any;
return mockGetBoundingClientRect({
left: 80,
top: 100,
width: 40,
height: 50,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return { left: 20, top: 30, width: 100, height: 100 } as any;
return mockGetBoundingClientRect({
left: 20,
top: 30,
width: 100,
height: 100,
});
}
);
cut.setBounds();
expect(cut.toJSON()).toEqual({
top: 70,
left: 60,
@ -39,7 +78,57 @@ describe('overlay', () => {
});
});
test('that out-of-bounds dimensions are fixed', () => {
test('toJSON, bottom right', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 200,
width: 100,
right: 10,
bottom: 20,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
jest.spyOn(
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return mockGetBoundingClientRect({
left: 80,
top: 100,
width: 40,
height: 50,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return mockGetBoundingClientRect({
left: 20,
top: 30,
width: 100,
height: 100,
});
}
);
cut.setBounds();
expect(cut.toJSON()).toEqual({
bottom: -20,
right: 0,
width: 40,
height: 50,
});
});
test('that out-of-bounds dimensions are fixed, top left', () => {
const container = document.createElement('div');
const content = document.createElement('div');
@ -61,14 +150,26 @@ describe('overlay', () => {
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return { left: 80, top: 100, width: 40, height: 50 } as any;
return mockGetBoundingClientRect({
left: 80,
top: 100,
width: 40,
height: 50,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return { left: 20, top: 30, width: 100, height: 100 } as any;
return mockGetBoundingClientRect({
left: 20,
top: 30,
width: 100,
height: 100,
});
}
);
cut.setBounds();
expect(cut.toJSON()).toEqual({
top: 70,
left: 60,
@ -77,7 +178,57 @@ describe('overlay', () => {
});
});
test('setBounds', () => {
test('that out-of-bounds dimensions are fixed, bottom right', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 200,
width: 100,
bottom: -1000,
right: -1000,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
jest.spyOn(
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return mockGetBoundingClientRect({
left: 80,
top: 100,
width: 40,
height: 50,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return mockGetBoundingClientRect({
left: 20,
top: 30,
width: 100,
height: 100,
});
}
);
cut.setBounds();
expect(cut.toJSON()).toEqual({
bottom: -20,
right: 0,
width: 40,
height: 50,
});
});
test('setBounds, top left', () => {
const container = document.createElement('div');
const content = document.createElement('div');
@ -101,11 +252,21 @@ describe('overlay', () => {
expect(element).toBeTruthy();
jest.spyOn(element, 'getBoundingClientRect').mockImplementation(() => {
return { left: 300, top: 400, width: 1000, height: 1000 } as any;
return mockGetBoundingClientRect({
left: 300,
top: 400,
width: 200,
height: 100,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return { left: 0, top: 0, width: 1000, height: 1000 } as any;
return mockGetBoundingClientRect({
left: 0,
top: 0,
width: 1000,
height: 1000,
});
}
);
@ -117,6 +278,56 @@ describe('overlay', () => {
expect(element.style.top).toBe('400px');
});
test('setBounds, bottom right', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 1000,
width: 1000,
right: 0,
bottom: 0,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
const element: HTMLElement = container.querySelector(
'.dv-resize-container'
)!;
expect(element).toBeTruthy();
jest.spyOn(element, 'getBoundingClientRect').mockImplementation(() => {
return mockGetBoundingClientRect({
left: 500,
top: 500,
width: 200,
height: 100,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return mockGetBoundingClientRect({
left: 0,
top: 0,
width: 1000,
height: 1000,
});
}
);
cut.setBounds({ height: 100, width: 200, right: 300, bottom: 400 });
expect(element.style.height).toBe('100px');
expect(element.style.width).toBe('200px');
expect(element.style.right).toBe('300px');
expect(element.style.bottom).toBe('400px');
});
test('that the resize handles are added', () => {
const container = document.createElement('div');
const content = document.createElement('div');

View File

@ -490,14 +490,11 @@ describe('tabsContainer', () => {
const eventPreventDefaultSpy = jest.spyOn(event, 'preventDefault');
fireEvent(container, event);
expect(accessor.addFloatingGroup).toHaveBeenCalledWith(
groupPanel,
{
x: 100,
y: 60,
},
{ inDragMode: true }
);
expect(accessor.addFloatingGroup).toHaveBeenCalledWith(groupPanel, {
x: 100,
y: 60,
inDragMode: true,
});
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1);
expect(eventPreventDefaultSpy).toHaveBeenCalledTimes(1);

View File

@ -1,4 +1,5 @@
import {
FloatingGroupOptions,
IDockviewComponent,
SerializedDockview,
} from '../dockview/dockviewComponent';
@ -42,7 +43,7 @@ import {
GroupDragEvent,
TabDragEvent,
} from '../dockview/components/titlebar/tabsContainer';
import { Box } from '../types';
import { AnchoredBox, Box } from '../types';
import {
DockviewDidDropEvent,
DockviewWillDropEvent,
@ -800,9 +801,9 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
*/
addFloatingGroup(
item: IDockviewPanel | DockviewGroupPanel,
coord?: { x: number; y: number }
options?: FloatingGroupOptions
): void {
return this.component.addFloatingGroup(item, coord);
return this.component.addFloatingGroup(item, options);
}
/**

View File

@ -1,3 +1,3 @@
export const DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE = 100;
export const DEFAULT_FLOATING_GROUP_POSITION = { left: 100, top: 100 };
export const DEFAULT_FLOATING_GROUP_POSITION = { left: 100, top: 100, width: 300, height: 300 };

View File

@ -11,7 +11,7 @@ import {
} from '../events';
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
import { clamp } from '../math';
import { Box } from '../types';
import { AnchoredBox } from '../types';
const bringElementToFront = (() => {
let previous: HTMLElement | null = null;
@ -40,6 +40,9 @@ export class Overlay extends CompositeDisposable {
private static MINIMUM_HEIGHT = 20;
private static MINIMUM_WIDTH = 20;
private verticalAlignment: 'top' | 'bottom' | undefined;
private horiziontalAlignment: 'left' | 'right' | undefined;
set minimumInViewportWidth(value: number | undefined) {
this.options.minimumInViewportWidth = value;
}
@ -49,7 +52,7 @@ export class Overlay extends CompositeDisposable {
}
constructor(
private readonly options: Box & {
private readonly options: AnchoredBox & {
container: HTMLElement;
content: HTMLElement;
minimumInViewportWidth?: number;
@ -78,23 +81,39 @@ export class Overlay extends CompositeDisposable {
this.setBounds({
height: this.options.height,
width: this.options.width,
top: this.options.top,
left: this.options.left,
...('top' in this.options && { top: this.options.top }),
...('bottom' in this.options && { bottom: this.options.bottom }),
...('left' in this.options && { left: this.options.left }),
...('right' in this.options && { right: this.options.right }),
});
}
setBounds(bounds: Partial<Box> = {}): void {
setBounds(bounds: Partial<AnchoredBox> = {}): void {
if (typeof bounds.height === 'number') {
this._element.style.height = `${bounds.height}px`;
}
if (typeof bounds.width === 'number') {
this._element.style.width = `${bounds.width}px`;
}
if (typeof bounds.top === 'number') {
if ('top' in bounds && typeof bounds.top === 'number') {
this._element.style.top = `${bounds.top}px`;
this._element.style.bottom = 'auto';
this.verticalAlignment = 'top';
}
if (typeof bounds.left === 'number') {
if ('bottom' in bounds && typeof bounds.bottom === 'number') {
this._element.style.bottom = `${bounds.bottom}px`;
this._element.style.top = 'auto';
this.verticalAlignment = 'bottom';
}
if ('left' in bounds && typeof bounds.left === 'number') {
this._element.style.left = `${bounds.left}px`;
this._element.style.right = 'auto';
this.horiziontalAlignment = 'left';
}
if ('right' in bounds && typeof bounds.right === 'number') {
this._element.style.right = `${bounds.right}px`;
this._element.style.left = 'auto';
this.horiziontalAlignment = 'right';
}
const containerRect = this.options.container.getBoundingClientRect();
@ -106,39 +125,77 @@ export class Overlay extends CompositeDisposable {
const xOffset = Math.max(0, this.getMinimumWidth(overlayRect.width));
// a minimum height of minimumViewportHeight must be inside the viewport
const yOffset =
typeof this.options.minimumInViewportHeight === 'number'
? Math.max(0, this.getMinimumHeight(overlayRect.height))
: 0;
const yOffset = Math.max(0, this.getMinimumHeight(overlayRect.height));
const left = clamp(
overlayRect.left - containerRect.left,
-xOffset,
Math.max(0, containerRect.width - overlayRect.width + xOffset)
);
if (this.verticalAlignment === 'top') {
const top = clamp(
overlayRect.top - containerRect.top,
-yOffset,
Math.max(0, containerRect.height - overlayRect.height + yOffset)
);
this._element.style.top = `${top}px`;
this._element.style.bottom = 'auto';
}
const top = clamp(
overlayRect.top - containerRect.top,
-yOffset,
Math.max(0, containerRect.height - overlayRect.height + yOffset)
);
if (this.verticalAlignment === 'bottom') {
const bottom = clamp(
containerRect.bottom - overlayRect.bottom,
-yOffset,
Math.max(0, containerRect.height - overlayRect.height + yOffset)
);
this._element.style.bottom = `${bottom}px`;
this._element.style.top = 'auto';
}
this._element.style.left = `${left}px`;
this._element.style.top = `${top}px`;
if (this.horiziontalAlignment === 'left') {
const left = clamp(
overlayRect.left - containerRect.left,
-xOffset,
Math.max(0, containerRect.width - overlayRect.width + xOffset)
);
this._element.style.left = `${left}px`;
this._element.style.right = 'auto';
}
if (this.horiziontalAlignment === 'right') {
const right = clamp(
containerRect.right - overlayRect.right,
-xOffset,
Math.max(0, containerRect.width - overlayRect.width + xOffset)
);
this._element.style.right = `${right}px`;
this._element.style.left = 'auto';
}
this._onDidChange.fire();
}
toJSON(): Box {
toJSON(): AnchoredBox {
const container = this.options.container.getBoundingClientRect();
const element = this._element.getBoundingClientRect();
return {
top: element.top - container.top,
left: element.left - container.left,
width: element.width,
height: element.height,
};
const result: any = {};
if (this.verticalAlignment === 'top') {
result.top = parseFloat(this._element.style.top);
} else if (this.verticalAlignment === 'bottom') {
result.bottom = parseFloat(this._element.style.bottom);
} else {
result.top = element.top - container.top;
}
if (this.horiziontalAlignment === 'left') {
result.left = parseFloat(this._element.style.left);
} else if (this.horiziontalAlignment === 'right') {
result.right = parseFloat(this._element.style.right);
} else {
result.left = element.left - container.left;
}
result.width = element.width;
result.height = element.height;
return result;
}
setupDrag(
@ -193,18 +250,7 @@ export class Overlay extends CompositeDisposable {
);
const yOffset = Math.max(
0,
this.options.minimumInViewportHeight
? this.getMinimumHeight(overlayRect.height)
: 0
);
const left = clamp(
x - offset.x,
-xOffset,
Math.max(
0,
containerRect.width - overlayRect.width + xOffset
)
this.getMinimumHeight(overlayRect.height)
);
const top = clamp(
@ -216,7 +262,53 @@ export class Overlay extends CompositeDisposable {
)
);
this.setBounds({ top, left });
const bottom = clamp(
offset.y -
y +
containerRect.height -
overlayRect.height,
-yOffset,
Math.max(
0,
containerRect.height - overlayRect.height + yOffset
)
);
const left = clamp(
x - offset.x,
-xOffset,
Math.max(
0,
containerRect.width - overlayRect.width + xOffset
)
);
const right = clamp(
offset.x - x + containerRect.width - overlayRect.width,
-xOffset,
Math.max(
0,
containerRect.width - overlayRect.width + xOffset
)
);
const bounds: any = {};
// Anchor to top or to bottom depending on which one is closer
if (top <= bottom) {
bounds.top = top;
} else {
bounds.bottom = bottom;
}
// Anchor to left or to right depending on which one is closer
if (left <= right) {
bounds.left = left;
} else {
bounds.right = right;
}
this.setBounds(bounds);
}),
addDisposableWindowListener(window, 'mouseup', () => {
toggleClass(
@ -342,8 +434,10 @@ export class Overlay extends CompositeDisposable {
}
let top: number | undefined = undefined;
let bottom: number | undefined = undefined;
let height: number | undefined = undefined;
let left: number | undefined = undefined;
let right: number | undefined = undefined;
let width: number | undefined = undefined;
const moveTop = () => {
@ -363,10 +457,13 @@ export class Overlay extends CompositeDisposable {
Overlay.MINIMUM_HEIGHT
)
);
height =
startPosition!.originalY +
startPosition!.originalHeight -
top;
bottom = containerRect.height - top - height;
};
const moveBottom = () => {
@ -384,6 +481,8 @@ export class Overlay extends CompositeDisposable {
: Overlay.MINIMUM_HEIGHT,
Number.MAX_VALUE
);
bottom = containerRect.height - top - height;
};
const moveLeft = () => {
@ -406,6 +505,8 @@ export class Overlay extends CompositeDisposable {
startPosition!.originalX +
startPosition!.originalWidth -
left;
right = containerRect.width - left - width;
};
const moveRight = () => {
@ -423,6 +524,8 @@ export class Overlay extends CompositeDisposable {
: Overlay.MINIMUM_WIDTH,
Number.MAX_VALUE
);
right = containerRect.width - left - width;
};
switch (direction) {
@ -456,7 +559,26 @@ export class Overlay extends CompositeDisposable {
break;
}
this.setBounds({ height, width, top, left });
const bounds: any = {};
// Anchor to top or to bottom depending on which one is closer
if (top! <= bottom!) {
bounds.top = top;
} else {
bounds.bottom = bottom;
}
// Anchor to left or to right depending on which one is closer
if (left! <= right!) {
bounds.left = left;
} else {
bounds.right = right;
}
bounds.height = height;
bounds.width = width;
this.setBounds(bounds);
}),
{
dispose: () => {
@ -485,7 +607,7 @@ export class Overlay extends CompositeDisposable {
if (typeof this.options.minimumInViewportHeight === 'number') {
return height - this.options.minimumInViewportHeight;
}
return height;
return 0;
}
override dispose(): void {

View File

@ -270,14 +270,11 @@ export class TabsContainer
const { top: rootTop, left: rootLeft } =
this.accessor.element.getBoundingClientRect();
this.accessor.addFloatingGroup(
this.group,
{
x: left - rootLeft + 20,
y: top - rootTop + 20,
},
{ inDragMode: true }
);
this.accessor.addFloatingGroup(this.group, {
x: left - rootLeft + 20,
y: top - rootTop + 20,
inDragMode: true,
});
}
}
),
@ -380,14 +377,11 @@ export class TabsContainer
const { top: rootTop, left: rootLeft } =
this.accessor.element.getBoundingClientRect();
this.accessor.addFloatingGroup(
panel as DockviewPanel,
{
x: left - rootLeft,
y: top - rootTop,
},
{ inDragMode: true }
);
this.accessor.addFloatingGroup(panel as DockviewPanel, {
x: left - rootLeft,
y: top - rootTop,
inDragMode: true,
});
return;
}

View File

@ -57,7 +57,7 @@ import {
GroupDragEvent,
TabDragEvent,
} from './components/titlebar/tabsContainer';
import { Box } from '../types';
import { AnchoredBox, Box } from '../types';
import {
DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE,
DEFAULT_FLOATING_GROUP_POSITION,
@ -126,7 +126,7 @@ export interface PanelReference {
export interface SerializedFloatingGroup {
data: GroupPanelViewState;
position: Box;
position: AnchoredBox;
}
export interface SerializedPopoutGroup {
@ -165,6 +165,17 @@ type MoveGroupOrPanelOptions = {
};
};
export interface FloatingGroupOptions {
x?: number;
y?: number;
height?: number;
width?: number;
position?: AnchoredBox;
skipRemoveGroup?: boolean;
inDragMode?: boolean;
skipActiveGroup?: boolean;
}
export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
readonly activePanel: IDockviewPanel | undefined;
readonly totalPanels: number;
@ -208,7 +219,7 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
//
addFloatingGroup(
item: IDockviewPanel | DockviewGroupPanel,
coord?: { x: number; y: number }
options?: FloatingGroupOptions
): void;
addPopoutGroup(
item: IDockviewPanel | DockviewGroupPanel,
@ -751,12 +762,7 @@ export class DockviewComponent
addFloatingGroup(
item: DockviewPanel | DockviewGroupPanel,
coord?: { x?: number; y?: number; height?: number; width?: number },
options?: {
skipRemoveGroup?: boolean;
inDragMode: boolean;
skipActiveGroup?: boolean;
}
options?: FloatingGroupOptions
): void {
let group: DockviewGroupPanel;
@ -817,22 +823,62 @@ export class DockviewComponent
group.model.location = { type: 'floating' };
const overlayLeft =
typeof coord?.x === 'number'
? Math.max(coord.x, 0)
: DEFAULT_FLOATING_GROUP_POSITION.left;
const overlayTop =
typeof coord?.y === 'number'
? Math.max(coord.y, 0)
: DEFAULT_FLOATING_GROUP_POSITION.top;
function getAnchoredBox(): AnchoredBox {
if (options?.position) {
const result: any = {};
if ('left' in options.position) {
result.left = Math.max(options.position.left, 0);
} else if ('right' in options.position) {
result.right = Math.max(options.position.right, 0);
} else {
result.left = DEFAULT_FLOATING_GROUP_POSITION.left;
}
if ('top' in options.position) {
result.top = Math.max(options.position.top, 0);
} else if ('bottom' in options.position) {
result.bottom = Math.max(options.position.bottom, 0);
} else {
result.top = DEFAULT_FLOATING_GROUP_POSITION.top;
}
if ('width' in options.position) {
result.width = Math.max(options.position.width, 0);
} else {
result.width = DEFAULT_FLOATING_GROUP_POSITION.width;
}
if ('height' in options.position) {
result.height = Math.max(options.position.height, 0);
} else {
result.height = DEFAULT_FLOATING_GROUP_POSITION.height;
}
return result as AnchoredBox;
}
return {
left:
typeof options?.x === 'number'
? Math.max(options.x, 0)
: DEFAULT_FLOATING_GROUP_POSITION.left,
top:
typeof options?.y === 'number'
? Math.max(options.y, 0)
: DEFAULT_FLOATING_GROUP_POSITION.top,
width:
typeof options?.width === 'number'
? Math.max(options.width, 0)
: DEFAULT_FLOATING_GROUP_POSITION.width,
height:
typeof options?.height === 'number'
? Math.max(options.height, 0)
: DEFAULT_FLOATING_GROUP_POSITION.height,
};
}
const anchoredBox = getAnchoredBox();
const overlay = new Overlay({
container: this.gridview.element,
content: group.element,
height: coord?.height ?? 300,
width: coord?.width ?? 300,
left: overlayLeft,
top: overlayTop,
...anchoredBox,
minimumInViewportWidth:
this.options.floatingGroupBounds === 'boundedWithinViewport'
? undefined
@ -972,7 +1018,7 @@ export class DockviewComponent
this.options.floatingGroupBounds?.minimumWidthWithinViewport;
}
group.overlay.setBounds({});
group.overlay.setBounds();
}
}
@ -1195,16 +1241,11 @@ export class DockviewComponent
const group = createGroupFromSerializedState(data);
this.addFloatingGroup(
group,
{
x: position.left,
y: position.top,
height: position.height,
width: position.width,
},
{ skipRemoveGroup: true, inDragMode: false }
);
this.addFloatingGroup(group, {
position: position,
skipRemoveGroup: true,
inDragMode: false,
});
}
const serializedPopoutGroups = data.popoutGroups ?? [];
@ -1387,7 +1428,8 @@ export class DockviewComponent
? options.floating
: {};
this.addFloatingGroup(group, o, {
this.addFloatingGroup(group, {
...o,
inDragMode: false,
skipRemoveGroup: true,
skipActiveGroup: true,
@ -1440,7 +1482,8 @@ export class DockviewComponent
? options.floating
: {};
this.addFloatingGroup(group, coordinates, {
this.addFloatingGroup(group, {
...coordinates,
inDragMode: false,
skipRemoveGroup: true,
skipActiveGroup: true,

View File

@ -1,37 +1,22 @@
import { Overlay } from '../dnd/overlay';
import { CompositeDisposable } from '../lifecycle';
import { AnchoredBox } from '../types';
import { DockviewGroupPanel, IDockviewGroupPanel } from './dockviewGroupPanel';
export interface IDockviewFloatingGroupPanel {
readonly group: IDockviewGroupPanel;
position(
bounds: Partial<{
top: number;
left: number;
height: number;
width: number;
}>
): void;
position(bounds: Partial<AnchoredBox>): void;
}
export class DockviewFloatingGroupPanel
extends CompositeDisposable
implements IDockviewFloatingGroupPanel
{
implements IDockviewFloatingGroupPanel {
constructor(readonly group: DockviewGroupPanel, readonly overlay: Overlay) {
super();
this.addDisposables(overlay);
}
position(
bounds: Partial<{
top: number;
left: number;
height: number;
width: number;
}>
): void {
position(bounds: Partial<AnchoredBox>): void {
this.overlay.setBounds(bounds);
}
}

View File

@ -14,6 +14,7 @@ import {
import { IDockviewPanel } from './dockviewPanel';
import { DockviewPanelRenderer } from '../overlayRenderContainer';
import { IGroupHeaderProps } from './framework';
import { AnchoredBox } from '../types';
export interface IHeaderActionsRenderer extends IDisposable {
readonly element: HTMLElement;
@ -37,11 +38,11 @@ export interface DockviewOptions {
singleTabMode?: 'fullwidth' | 'default';
disableFloatingGroups?: boolean;
floatingGroupBounds?:
| 'boundedWithinViewport'
| {
minimumHeightWithinViewport?: number;
minimumWidthWithinViewport?: number;
};
| 'boundedWithinViewport'
| {
minimumHeightWithinViewport?: number;
minimumWidthWithinViewport?: number;
};
popoutUrl?: string;
defaultRenderer?: DockviewPanelRenderer;
debug?: boolean;
@ -74,7 +75,7 @@ export class DockviewUnhandledDragOverEvent implements DockviewDndOverlayEvent {
readonly position: Position,
readonly getData: () => PanelTransfer | undefined,
readonly group?: DockviewGroupPanel
) {}
) { }
accept(): void {
this._isAccepted = true;
@ -176,13 +177,8 @@ export function isPanelOptionsWithGroup(
type AddPanelFloatingGroupUnion = {
floating:
| {
height?: number;
width?: number;
x?: number;
y?: number;
}
| true;
| Partial<AnchoredBox>
| true;
position: never;
};

View File

@ -8,3 +8,13 @@ export interface Box {
height: number;
width: number;
}
type TopLeft = { top: number; left: number };
type TopRight = { top: number; right: number };
type BottomLeft = { bottom: number; left: number };
type BottomRight = { bottom: number; right: number };
type AnchorPosition = TopLeft | TopRight | BottomLeft | BottomRight;
type Size = { width: number; height: number };
export type AnchoredBox = Size & AnchorPosition;

View File

@ -165,6 +165,6 @@ api.addPanel({
api.addPanel({
id: 'panel_2',
component: 'default',
floating: { x: 10, y: 10, width: 300, height: 300 },
floating: { left: 10, top: 10, width: 300, height: 300 },
});
```

View File

@ -88,7 +88,14 @@ const GroupAction = (props: {
}
onClick={() => {
if (group) {
props.api.addFloatingGroup(group);
props.api.addFloatingGroup(group, {
position: {
width: 400,
height: 300,
top: 50,
right: 50,
},
});
}
}}
>

View File

@ -74,7 +74,14 @@ const PanelAction = (props: {
onClick={() => {
const panel = props.api.getPanel(props.panelId);
if (panel) {
props.api.addFloatingGroup(panel);
props.api.addFloatingGroup(panel, {
position: {
width: 400,
height: 300,
bottom: 50,
right: 50,
},
});
}
}}
>

View File

@ -84,7 +84,7 @@ function addFloatingPanel2(api: DockviewApi) {
id: (++panelCount).toString(),
title: `Tab ${panelCount}`,
component: 'default',
floating: { width: 250, height: 150, x: 50, y: 50 },
floating: { width: 250, height: 150, left: 50, top: 50 },
});
}
@ -259,11 +259,9 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
);
React.useEffect(() => {
const disposable = props.group.api.onDidLocationChange(
(event) => {
setFloating(event.location.type === 'floating');
}
);
const disposable = props.group.api.onDidLocationChange((event) => {
setFloating(event.location.type === 'floating');
});
return () => {
disposable.dispose();
@ -275,7 +273,14 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
const group = props.containerApi.addGroup();
props.group.api.moveTo({ group });
} else {
props.containerApi.addFloatingGroup(props.group);
props.containerApi.addFloatingGroup(props.group, {
position: {
width: 400,
height: 300,
bottom: 50,
right: 50,
},
});
}
};

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@ export function defaultConfig(api: DockviewApi) {
id: 'panel_1',
component: 'default',
renderer: 'always',
title: 'Panel 1',
title: 'Panel 1'
});
api.addPanel({

View File

@ -32,7 +32,14 @@ export const GroupActions = (props: {
onClick={() => {
const panel = props.api?.getGroup(x);
if (panel) {
props.api?.addFloatingGroup(panel);
props.api?.addFloatingGroup(panel, {
position: {
width: 400,
height: 300,
bottom: 50,
right: 50,
},
});
}
}}
>

View File

@ -32,7 +32,14 @@ export const PanelActions = (props: {
onClick={() => {
const panel = props.api?.getPanel(x);
if (panel) {
props.api?.addFloatingGroup(panel);
props.api?.addFloatingGroup(panel, {
position: {
width: 400,
height: 300,
bottom: 50,
right: 50,
},
});
}
}}
>

View File

@ -209,9 +209,8 @@ export const DockviewPersistence = (props: { theme?: string }) => {
setDisableFloatingGroups((x) => !x);
}}
>
{`${
disableFloatingGroups ? 'Enable' : 'Disable'
} floating groups`}
{`${disableFloatingGroups ? 'Enable' : 'Disable'
} floating groups`}
</button>
</div>
<div
@ -265,7 +264,14 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
const group = props.containerApi.addGroup();
props.group.api.moveTo({ group });
} else {
props.containerApi.addFloatingGroup(props.group);
props.containerApi.addFloatingGroup(props.group, {
position: {
width: 400,
height: 300,
bottom: 50,
right: 50,
}
});
}
};

View File

@ -93,7 +93,14 @@ const RightAction = defineComponent({
const group = this.params.containerApi.addGroup();
this.group.api.moveTo({ group });
} else {
this.containerApi.addFloatingGroup(this.params.group);
this.containerApi.addFloatingGroup(this.params.group, {
position: {
width: 400,
height: 300,
bottom: 50,
right: 50,
},
});
}
},
},