mirror of
https://github.com/mathuo/dockview
synced 2025-08-28 21:16:38 +00:00
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:
commit
f20b5285da
@ -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');
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,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;
|
||||
|
@ -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 },
|
||||
});
|
||||
```
|
||||
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -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
@ -14,7 +14,7 @@ export function defaultConfig(api: DockviewApi) {
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
renderer: 'always',
|
||||
title: 'Panel 1',
|
||||
title: 'Panel 1'
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -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,
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user