mirror of
https://github.com/mathuo/dockview
synced 2025-02-08 17:35:44 +00:00
Support bottom and right anchor for Overlay
This commit is contained in:
parent
ce381f8ce9
commit
bbdb99dbb5
@ -1,7 +1,14 @@
|
||||
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,11 +30,11 @@ 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 });
|
||||
}
|
||||
);
|
||||
|
||||
@ -39,7 +46,45 @@ 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 });
|
||||
}
|
||||
);
|
||||
|
||||
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,11 +106,11 @@ 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 });
|
||||
}
|
||||
);
|
||||
|
||||
@ -77,7 +122,45 @@ 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 });
|
||||
}
|
||||
);
|
||||
|
||||
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 +184,11 @@ 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 +200,46 @@ 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');
|
||||
|
@ -42,7 +42,7 @@ import {
|
||||
GroupDragEvent,
|
||||
TabDragEvent,
|
||||
} from '../dockview/components/titlebar/tabsContainer';
|
||||
import { Box } from '../types';
|
||||
import { AnchoredBox, Box } from '../types';
|
||||
import {
|
||||
DockviewDidDropEvent,
|
||||
DockviewWillDropEvent,
|
||||
@ -139,7 +139,7 @@ export class SplitviewApi implements CommonApi<SerializedSplitview> {
|
||||
return this.component.onDidRemoveView;
|
||||
}
|
||||
|
||||
constructor(private readonly component: ISplitviewComponent) {}
|
||||
constructor(private readonly component: ISplitviewComponent) { }
|
||||
|
||||
/**
|
||||
* Update configuratable options.
|
||||
@ -295,7 +295,7 @@ export class PaneviewApi implements CommonApi<SerializedPaneview> {
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
constructor(private readonly component: IPaneviewComponent) {}
|
||||
constructor(private readonly component: IPaneviewComponent) { }
|
||||
|
||||
/**
|
||||
* Remove a panel given the panel object.
|
||||
@ -459,7 +459,7 @@ export class GridviewApi implements CommonApi<SerializedGridviewComponent> {
|
||||
this.component.updateOptions({ orientation: value });
|
||||
}
|
||||
|
||||
constructor(private readonly component: IGridviewComponent) {}
|
||||
constructor(private readonly component: IGridviewComponent) { }
|
||||
|
||||
/**
|
||||
* Focus the component. Will try to focus an active panel if one exists.
|
||||
@ -728,7 +728,7 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
return this.component.activeGroup;
|
||||
}
|
||||
|
||||
constructor(private readonly component: IDockviewComponent) {}
|
||||
constructor(private readonly component: IDockviewComponent) { }
|
||||
|
||||
/**
|
||||
* Focus the component. Will try to focus an active panel if one exists.
|
||||
@ -800,9 +800,15 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
*/
|
||||
addFloatingGroup(
|
||||
item: IDockviewPanel | DockviewGroupPanel,
|
||||
coord?: { x: number; y: number }
|
||||
coord?: { x: number; y: number },
|
||||
options?: {
|
||||
position?: AnchoredBox;
|
||||
skipRemoveGroup?: boolean;
|
||||
inDragMode?: boolean;
|
||||
skipActiveGroup?: boolean;
|
||||
}
|
||||
): void {
|
||||
return this.component.addFloatingGroup(item, coord);
|
||||
return this.component.addFloatingGroup(item, coord, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 };
|
||||
|
@ -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;
|
||||
@ -49,7 +49,7 @@ export class Overlay extends CompositeDisposable {
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly options: Box & {
|
||||
private readonly options: AnchoredBox & {
|
||||
container: HTMLElement;
|
||||
content: HTMLElement;
|
||||
minimumInViewportWidth?: number;
|
||||
@ -78,23 +78,35 @@ 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";
|
||||
}
|
||||
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";
|
||||
}
|
||||
if ("left" in bounds && typeof bounds.left === 'number') {
|
||||
this._element.style.left = `${bounds.left}px`;
|
||||
this._element.style.right = "auto";
|
||||
}
|
||||
if ("right" in bounds && typeof bounds.right === 'number') {
|
||||
this._element.style.right = `${bounds.right}px`;
|
||||
this._element.style.left = "auto";
|
||||
}
|
||||
|
||||
const containerRect = this.options.container.getBoundingClientRect();
|
||||
@ -106,39 +118,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 ("top" in bounds && typeof bounds.top === 'number') {
|
||||
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 ("bottom" in bounds && typeof bounds.bottom === 'number') {
|
||||
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 ("left" in bounds && typeof bounds.left === 'number') {
|
||||
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 ("right" in bounds && typeof bounds.right === 'number') {
|
||||
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._element.style.top !== "auto") {
|
||||
result.top = parseFloat(this._element.style.top);
|
||||
} else if (this._element.style.bottom !== "auto") {
|
||||
result.bottom = parseFloat(this._element.style.bottom);
|
||||
} else {
|
||||
result.top = element.top - container.top;
|
||||
}
|
||||
|
||||
if (this._element.style.left !== "auto") {
|
||||
result.left = parseFloat(this._element.style.left);
|
||||
} else if (this._element.style.right !== "auto") {
|
||||
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 +243,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 +255,50 @@ 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 +424,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 = () => {
|
||||
@ -353,20 +437,21 @@ export class Overlay extends CompositeDisposable {
|
||||
startPosition!.originalY +
|
||||
startPosition!.originalHeight >
|
||||
containerRect.height
|
||||
? this.getMinimumHeight(
|
||||
containerRect.height
|
||||
)
|
||||
? this.getMinimumHeight(containerRect.height)
|
||||
: Math.max(
|
||||
0,
|
||||
startPosition!.originalY +
|
||||
startPosition!.originalHeight -
|
||||
Overlay.MINIMUM_HEIGHT
|
||||
)
|
||||
0,
|
||||
startPosition!.originalY +
|
||||
startPosition!.originalHeight -
|
||||
Overlay.MINIMUM_HEIGHT
|
||||
)
|
||||
);
|
||||
|
||||
height =
|
||||
startPosition!.originalY +
|
||||
startPosition!.originalHeight -
|
||||
top;
|
||||
|
||||
bottom = containerRect.height - top - height;
|
||||
};
|
||||
|
||||
const moveBottom = () => {
|
||||
@ -380,10 +465,12 @@ export class Overlay extends CompositeDisposable {
|
||||
typeof this.options
|
||||
.minimumInViewportHeight === 'number'
|
||||
? -top +
|
||||
this.options.minimumInViewportHeight
|
||||
this.options.minimumInViewportHeight
|
||||
: Overlay.MINIMUM_HEIGHT,
|
||||
Number.MAX_VALUE
|
||||
);
|
||||
|
||||
bottom = containerRect.height - top - height;
|
||||
};
|
||||
|
||||
const moveLeft = () => {
|
||||
@ -395,17 +482,19 @@ export class Overlay extends CompositeDisposable {
|
||||
containerRect.width
|
||||
? this.getMinimumWidth(containerRect.width)
|
||||
: Math.max(
|
||||
0,
|
||||
startPosition!.originalX +
|
||||
startPosition!.originalWidth -
|
||||
Overlay.MINIMUM_WIDTH
|
||||
)
|
||||
0,
|
||||
startPosition!.originalX +
|
||||
startPosition!.originalWidth -
|
||||
Overlay.MINIMUM_WIDTH
|
||||
)
|
||||
);
|
||||
|
||||
width =
|
||||
startPosition!.originalX +
|
||||
startPosition!.originalWidth -
|
||||
left;
|
||||
|
||||
right = containerRect.width - left - width;
|
||||
};
|
||||
|
||||
const moveRight = () => {
|
||||
@ -419,10 +508,12 @@ export class Overlay extends CompositeDisposable {
|
||||
typeof this.options
|
||||
.minimumInViewportWidth === 'number'
|
||||
? -left +
|
||||
this.options.minimumInViewportWidth
|
||||
this.options.minimumInViewportWidth
|
||||
: Overlay.MINIMUM_WIDTH,
|
||||
Number.MAX_VALUE
|
||||
);
|
||||
|
||||
right = containerRect.width - left - width;
|
||||
};
|
||||
|
||||
switch (direction) {
|
||||
@ -456,7 +547,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 +595,7 @@ export class Overlay extends CompositeDisposable {
|
||||
if (typeof this.options.minimumInViewportHeight === 'number') {
|
||||
return height - this.options.minimumInViewportHeight;
|
||||
}
|
||||
return height;
|
||||
return 0;
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
|
@ -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 {
|
||||
@ -208,7 +208,10 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
||||
//
|
||||
addFloatingGroup(
|
||||
item: IDockviewPanel | DockviewGroupPanel,
|
||||
coord?: { x: number; y: number }
|
||||
coord?: { x: number; y: number },
|
||||
options?: {
|
||||
position?: AnchoredBox
|
||||
}
|
||||
): void;
|
||||
addPopoutGroup(
|
||||
item: IDockviewPanel | DockviewGroupPanel,
|
||||
@ -223,8 +226,7 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
||||
|
||||
export class DockviewComponent
|
||||
extends BaseGrid<DockviewGroupPanel>
|
||||
implements IDockviewComponent
|
||||
{
|
||||
implements IDockviewComponent {
|
||||
private readonly nextGroupId = sequentialNumberGenerator();
|
||||
private readonly _deserializer = new DefaultDockviewDeserialzier(this);
|
||||
private readonly _api: DockviewApi;
|
||||
@ -750,6 +752,7 @@ export class DockviewComponent
|
||||
item: DockviewPanel | DockviewGroupPanel,
|
||||
coord?: { x?: number; y?: number; height?: number; width?: number },
|
||||
options?: {
|
||||
position?: AnchoredBox;
|
||||
skipRemoveGroup?: boolean;
|
||||
inDragMode: boolean;
|
||||
skipActiveGroup?: boolean;
|
||||
@ -814,34 +817,70 @@ 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 coord?.x === 'number'
|
||||
? Math.max(coord.x, 0)
|
||||
: DEFAULT_FLOATING_GROUP_POSITION.left,
|
||||
top: typeof coord?.y === 'number'
|
||||
? Math.max(coord.y, 0)
|
||||
: DEFAULT_FLOATING_GROUP_POSITION.top,
|
||||
width: typeof coord?.width === 'number'
|
||||
? Math.max(coord.width, 0)
|
||||
: DEFAULT_FLOATING_GROUP_POSITION.width,
|
||||
height: typeof coord?.height === 'number'
|
||||
? Math.max(coord.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
|
||||
: this.options.floatingGroupBounds
|
||||
?.minimumWidthWithinViewport ??
|
||||
DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE,
|
||||
?.minimumWidthWithinViewport ??
|
||||
DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE,
|
||||
minimumInViewportHeight:
|
||||
this.options.floatingGroupBounds === 'boundedWithinViewport'
|
||||
? undefined
|
||||
: this.options.floatingGroupBounds
|
||||
?.minimumHeightWithinViewport ??
|
||||
DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE,
|
||||
?.minimumHeightWithinViewport ??
|
||||
DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE,
|
||||
});
|
||||
|
||||
const el = group.element.querySelector('.void-container');
|
||||
@ -1194,13 +1233,8 @@ export class DockviewComponent
|
||||
|
||||
this.addFloatingGroup(
|
||||
group,
|
||||
{
|
||||
x: position.left,
|
||||
y: position.top,
|
||||
height: position.height,
|
||||
width: position.width,
|
||||
},
|
||||
{ skipRemoveGroup: true, inDragMode: false }
|
||||
undefined,
|
||||
{ position: position, skipRemoveGroup: true, inDragMode: false }
|
||||
);
|
||||
}
|
||||
|
||||
@ -1338,7 +1372,7 @@ export class DockviewComponent
|
||||
referenceGroup =
|
||||
typeof options.position.referenceGroup === 'string'
|
||||
? this._groups.get(options.position.referenceGroup)
|
||||
?.value
|
||||
?.value
|
||||
: options.position.referenceGroup;
|
||||
|
||||
if (!referenceGroup) {
|
||||
@ -1380,7 +1414,7 @@ export class DockviewComponent
|
||||
|
||||
const o =
|
||||
typeof options.floating === 'object' &&
|
||||
options.floating !== null
|
||||
options.floating !== null
|
||||
? options.floating
|
||||
: {};
|
||||
|
||||
@ -1433,7 +1467,7 @@ export class DockviewComponent
|
||||
|
||||
const coordinates =
|
||||
typeof options.floating === 'object' &&
|
||||
options.floating !== null
|
||||
options.floating !== null
|
||||
? options.floating
|
||||
: {};
|
||||
|
||||
@ -1471,9 +1505,9 @@ export class DockviewComponent
|
||||
skipDispose: boolean;
|
||||
skipSetActiveGroup?: boolean;
|
||||
} = {
|
||||
removeEmptyGroup: true,
|
||||
skipDispose: false,
|
||||
}
|
||||
removeEmptyGroup: true,
|
||||
skipDispose: false,
|
||||
}
|
||||
): void {
|
||||
const group = panel.group;
|
||||
|
||||
@ -1538,8 +1572,8 @@ export class DockviewComponent
|
||||
const referencePanel =
|
||||
typeof options.referencePanel === 'string'
|
||||
? this.panels.find(
|
||||
(panel) => panel.id === options.referencePanel
|
||||
)
|
||||
(panel) => panel.id === options.referencePanel
|
||||
)
|
||||
: options.referencePanel;
|
||||
|
||||
if (!referencePanel) {
|
||||
@ -1604,11 +1638,11 @@ export class DockviewComponent
|
||||
group: DockviewGroupPanel,
|
||||
options?:
|
||||
| {
|
||||
skipActive?: boolean;
|
||||
skipDispose?: boolean;
|
||||
skipPopoutAssociated?: boolean;
|
||||
skipPopoutReturn?: boolean;
|
||||
}
|
||||
skipActive?: boolean;
|
||||
skipDispose?: boolean;
|
||||
skipPopoutAssociated?: boolean;
|
||||
skipPopoutReturn?: boolean;
|
||||
}
|
||||
| undefined
|
||||
): void {
|
||||
this.doRemoveGroup(group, options);
|
||||
@ -1618,11 +1652,11 @@ export class DockviewComponent
|
||||
group: DockviewGroupPanel,
|
||||
options?:
|
||||
| {
|
||||
skipActive?: boolean;
|
||||
skipDispose?: boolean;
|
||||
skipPopoutAssociated?: boolean;
|
||||
skipPopoutReturn?: boolean;
|
||||
}
|
||||
skipActive?: boolean;
|
||||
skipDispose?: boolean;
|
||||
skipPopoutAssociated?: boolean;
|
||||
skipPopoutReturn?: boolean;
|
||||
}
|
||||
| undefined
|
||||
): DockviewGroupPanel {
|
||||
const panels = [...group.panels]; // reassign since group panels will mutate
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -8,3 +8,7 @@ export interface Box {
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export type AnchoredBox =
|
||||
({ top: number, height: number } | { bottom: number, height: number }) &
|
||||
({ left: number, width: number } | { right: number, width: number });
|
@ -29,7 +29,14 @@ const PanelAction = (props: {
|
||||
onClick={() => {
|
||||
const panel = props.api.getPanel(props.panelId);
|
||||
if (panel) {
|
||||
props.api.addFloatingGroup(panel);
|
||||
props.api.addFloatingGroup(panel, undefined, {
|
||||
position: {
|
||||
width: 400,
|
||||
height: 300,
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -14,7 +14,7 @@ export function defaultConfig(api: DockviewApi) {
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
renderer: 'always',
|
||||
title: 'Panel 1',
|
||||
title: 'Panel 1'
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
|
Loading…
Reference in New Issue
Block a user